OAuth2 Groundhog Day

Over the last 5 years or so, I have had countless discussions about OAuth2 which cover the same topics, and commonly start with reasons why SAML2 either isn’t:

  • Feasible, due to third party software support.
  • Preferred, for reasons up to and including a developer aversion to XML.

Before I go any further, I will hold my hand up and say that I have no doubt done my bit to complicate those discussions, having initially viewed OAuth2 rather suspiciously.

Where things get interesting is when we are federating with ourselves, which is a really common use case. There are a few things in the OAuth2 specs that are either not explicit or are commonly reinterpreted, which complicate this. I’m going to touch on a couple of them.

The Client. No, Not That Client
By way of introduction I’m going to start with something which is well defined: the OAuth2 client which, depending on location, has different trust implications. Beyond this, your implementation is bound up on whether or not you are dealing with a human, and then the appropriateness of holding a client secret. This is ignoring the password grant which is best used… never. So the defining characteristics of the client, and by extension which grant, are well trodden ground. There are plenty of great resources that flowchart out what you need to do.

Delegation
This is supposed to be one of the core tenets of OAuth2 for the human user centric flows. However, what it actually means starts to get a little hazy when we are federating with ourselves, so much so that it commonly gets dropped on the floor. We gloss over the fact that while the AC grant client is doing a couple of protocol specific things like kicking off the flow, and doing the out of band token exchange, it is also commonly embedded in the same application as the resource server. So, we aren’t really delegating any more: there is no third party in play. I think an interesting acid test for this if you get into an AC grant implementation discussion about the optionality of the consent form. While you can use OAuth2 for your application, you probably aren’t using it as intended.

Scopes and Claims
Ignoring implementations which present access tokens the same way (i.e., as a meaningful token rather than a random string), there are pragmatic advantages to the use of OIDC, principally, because you don’t need to talk to the Authorisation Server to validate the JWT. This high degree of portability introduces the possibility of the application which received the JWT bundling it up and presenting it, in the context of the original user, to a downstream API.

There is nothing stopping the server doing exactly the same thing with an access token. The only distinguishing feature is how scope is presented:

  • Assuming an opaque token, the scope is in the response from the authorisation server
  • For a JWT, the scope (the group of claims) is baked in.

(The Authorisation Server may of course respond, based on client id / secret and say ‘this isn’t for you’.)

Scopes in OAuth2 aren’t standardised, and the standard claims in OIDC aren’t much use, so you are on your own here. You have to invent some sort of naming scheme that probably applies on a per resource server / relying party basis. This is especially compelling if the consuming entity is an API: you just come up with a list.

What about representing human entitlement? The easiest thing to do is treat the custom claim data or scope the same way we commonly use a SAML response: as something to get in the front door with. Starting with the per application scope, you extract the identity, you map it onto something internal – including locally expressed entitlement data – and you are off to the races. You can request multiple scopes, and still get an access token which, when it is evaluated, will return a list. Doing this for a human, and not presenting the consent screen, is really starting to drift away from the spirit of the standard – the authorisation part.

Microservices and Delegation Again
Rather than talk about microservices specifically, I am going to generalise this to passing the same bearer token around between APIs. It’s a problem that’s as old as the hills. Rather than getting into Kerberos delegation, I’ll draw on another old-tech example closer to the topic at hand: I remember being pretty surprised to find that the website-specific cookies minted by the proprietary SSO service that I managed were starting to be pushed onto a SOA service bus. Today, authentication infrastructure owners are probably facing comparable use cases but with JWTs due to their portability. They are being passed around, whether they like it or not (they may not even know). Going back to JWT claims, I think the best that you could hope for is putting some sort of broad-grained entitlement information about the user in it, and hoping that it is being evaluated meaningfully when it lands on the downstream API.

This speaks to a rather freeform development environment; I am sure that there are organisations that either address token usage through policy, or are building microservices on more uniform application stacks that allows them to dig into scopes / claims in a more meaningful way. I just don’t think it’s universal.

Sessions
Again, this is way outside the intent of the standards, but it’s an occasional topic: for stateful applications, inferring a user’s session lifetime based on a token or JWT. While SAML responses have a bunch of rather confusing timestamps, the intent is really just to get them into the app with some leeway for transport delays. The assertion never goes any further than the SP.

Applying a loose interpretation of the principle of least privilege, not sending the token all the way back to the browser as a cookie has got to be considered. However, for single page apps talking directly to APIs this is impractical.

Finally on session management, a passing shot at refresh tokens. Is revocation that big a problem? I don’t think so; at least not one that couldn’t be solved in a better way.

So Where Does This Leave us?
As I’ve touched on before, over a period of years, the industry has settled on a pattern of use with SAML2 which represents a relatively small subset of the original specs, but it is still slowly evolving. When I started using it about 15 years ago, doing a certificate (or even a public key) exchange with the SP for request signing was considered a necessity. Confidence in the controls applied by the IDP to stop it from being an open relay, and at the same time halving the headache of managing certificate expiry, has seen the practice start to fall out of default usage.

Conversely, I think we still have a way to go with OAuth2 and OIDC to reach a point where default patterns are just plug and play. This is almost inevitable given that both what’s in the spec is open to interpretation, and the amount of work you have to do with what’s not in it.

First chrome extension…

I’ve hacked together a quick [well, it took me all bloody afternoon, but it’s my first go] extension which does something pretty simple: when the button on the toolbar is pressed, it loops through all of the cookies for the domain [for the current tab], deletes them all and reloads the page.

This pointed me in the right direction, but I couldn’t figure out how [or for that matter why] to use the background.js to implement the delete function, so it’s called directly from the standard popup.js.

There is one part of it which is not very reliable:

var domain = new URL(tab.url).hostname;
// Almost certainly unreliable!
var domainNoDubs = domain.match(/.*?([\w]+.[\w]+)$/)[1];

…which is basically an attempt to convert a domain www.site.com into site.com. There is a bunch of stuff that can go wrong here and my reliance on the ‘1th’ item in the array returned is pretty flaky. But, having tested it exhaustively on 3 different sites [!], it works. The reason I need to do this is because this call..

chrome.cookies.getAll({domain: domainNoDubs}, function(cookies) {...

…requires the passing in of a domain prefixed with a dot. Simply replacing ‘www‘ with nothing isn’t good enough. What this parameter actually needs to be is the dot-prefixed second level domain – or the highest part of it if it has something in front of it. So for www.this.site.com, it would actually need to be .site.com.

As I say, it works. More or less… Source is here.

Monitoring Humidity…

TL;DR

This boils down to:

  • A Raspberry Pi with a humidity sensor attached
  • Two Python scripts: one for a web endpoint, and another to generate push notifications running from cron.
  • A simple iPhone app to receive the push notifications and to graph the humidity data.
App Screenshot…

I have a small collection of guitars, one of which suffered damage from humidity over the winter. It was a tough lesson: I had no idea that they were so sensitive. I’ve subsequently bought another instrument which I’m now nervous about keeping out of its case.

Having looked unsuccessfully on Amazon for USB connected humidity sensors, I decided to re-purpose a sensor which I’d been using with an Arduino a couple of years back, and which had subsequently been consigned to the attic. I was reasonably satisfied with my first pass: the connection to the GPIO pins on the Raspberry Pi is a lot less faff than processing serial data over USB from the Arduino. I wrote a Python script which ran from cron every hour, and if either the temperature or humidity was over / under a threshold, it would email me. Side by side testing with a borrowed standalone sensor suggested it was reading consistently a few degrees hotter. The only male-to-female jumper wires I had lying around were pretty short, so the most obvious culprit was the heat from the Pi. I ordered some longer ones — and duly broke the sensor somehow when I reconnected it. Doh!

I ordered up a replacement [GY-BME280] which seems a lot more sensitive. A hot spell of weather suggested that something less intrusive than email would be better, so I thought I’d have a quick play with iOS push notifications. I ended up using OneSignal, who have a generous free tier. One slight constraint is that their Python library is for V2 only, and the dependencies for the sensor had already committed me to V3. Having gone through the app setup on OneSignal’s admin interface, and a quick browse of their API [simple for my purposes], I use requests to send the notification instead:

if (ALERT):
   post_body ={
      "app_id": "app-id-from-One-Signal",
      "headings": {"en": "Indoor weather warning!"},
      "contents":  {"en": CONTENT_TXT},
      "included_segments": ["Active Users"]
   }
   r = requests.post(url = pushEndpoint, headers={'Authorization': 'Basic api-key-from-OneSignal'},
 json=post_body)
 

Obviously the vendor library takes care of the exact syntax of the Authorisation header. That took a bit of testing to see exactly what was required when rolling my own API call.

I generate the CONTENT_TXT based on some if / elif statements earlier in the code. I took a different approach when I was using email: the content was additive, so if both the temperature and humidity were over the threshold, I’d get all of the info. Now, the first one to trigger wins as there isn’t a lot of real estate in the notification window. As an aside, the code above doesn’t process the response from the OneSignal endpoint. If it failed for some reason, I could resort to email, but that is a little circular for my implementation.

It’s rather one-dimensional having a boilerplate app on the phone which does nothing other than receive the push notifications, so charting seemed like a sensible addition. The state of the art has certainly moved on from when I first used charting in my weight tracking app [it was 7 years ago!]. There are a couple of components to this: the first is a very simple web service with a couple of endpoints [using Flask], the first of which returns historic data generated by a script called by cron – which I’ll come back to – and the second which calls a script to get live data from the sensor.

The cron script – the same one that generates the push messages – retains the last 24 hours of data on a rolling basis. I managed to make pretty heavy work of this, but here’s what I’ve come up with:

 with open(dataFile) as json_file:
         data = json.load(json_file)
         json_file.close()
         data['allData'].append(dataPoint)
         currentLength = len(data['allData'])
         if currentLength > 24:
             # This should always only be deleting the single, front-most element:
             delta = currentLength - 24
             trimmedData = data['allData'][delta:]
             trimmedData = {"allData":  trimmedData}
             data = trimmedData
         with open(dataFile, 'w') as outfile:
             json.dump(data, outfile)
             outfile.close()

The JSON structure is an outer dictionary with a single value, the array of ‘dataPoint’ dictionaries, where each of those is a single reading. Where I got myself into a bit of trouble was with this line:

trimmedData = data['allData'][delta:]

…which returns the ‘inner’ array reduced by the delta number of values from the front, rather than the ‘outer’ dictionary which wraps the array. There is probably a better way of doing this – not using the outer dictionary, for instance – but it works.

Programmatically, the iPhone app itself is pretty trivial. The more complicated part – relatively – is stepping through the install and config of the OneSignal SDK [plus admin processes on their web UI] and cutting the push certificate, but it’s all well documented.

I’m still clinging on to writing Objective-C which is starting to feel akin to admitting that I like to code in Latin. One aspect of Charts which took some digging into was how to put custom labels on an axis – in Objective-C. At the same time I parse out the readings into a mutable array of data entries…

[hums addObject:[[ChartDataEntry alloc] initWithX:xCounter y:humidity]];

…I also grab the timestamps – actually just the hours:minutes – into another mutable array and then…

 lineChart.xAxis.valueFormatter = [ChartIndexAxisValueFormatter withValues:timeLabels];

The Buried Bodies

Functionally, that’s pretty much it. There are a couple of extra decorations, the most significant of which is only attempting to haul the data down from the endpoints when the phone is connected to my home network. While I have the facility to carve out a DMZ on my network, this endpoint isn’t going to be the trigger for me to take the plunge. One body I’ve buried up to this point is that the Python scripts which connect to the GPIO need to run as root. There are ways round this, but it’s a utility vs effort trade-off thing. The same goes for using Flask, which is calling a script which runs as root.

Well I work in security, so it’s nice to let my hair down at weekends 🙂 .

The second minor detail of this implementation is that unfortunately Apple doesn’t allow the configuration of the push notification capability without signing up as a full developer. When I deleted my apps from the Apple Store last year, I decided to let my developer subscription expire.

While you can do quite a lot without it, having to recompile apps every 3 months or so can be a nuisance – something that was reinforced when my wife was asking me what I was doing on my Mac 10 minutes before a taxi was due to take us to the airport last week. The answer: re-compiling the iOS password manager that I wrote, and am quite reliant on when travelling. Instinctively, I knew this was the wrong answer 🙂 .

Anyway, I’ve signed up for another year.