On Being Almost There
James Uther
2015-06-30
Personis is an ongoing line of research projects about how we can store personal data (thing location tracking, fitness trackers, etc) in a way that leaves us in control of our data but at the same time allows us to give permission to useful services to process that data for us. A canonical example would be something like:
In which location and activity data are co-located somewhere which gives us ownership of that data and the ability to repurpose it. In this case location (track.er) and movement (pedomet.er) services are feeding data into personis, and HipsterFitnessVisualisat.ion (H25n). ⊕ I don't know if .er, .ce, .cha or .ion are valid gTLDs but given that ICANN seem to be just feeding a scrabble dictionary into the root servers, they will be soon. Google is using the data to show groovy single-origin-latte-splattered D3 animations of my lack of health.
Research themes might be around is Personis a data store or broker? What are the implications for me when I allow H25n into my data? What are useful ways of presenting these implications so I can make an informed choice? What APIs work best?
Anyhoo, this isn't a story about that. But I once added Oauth2 support to the personis server. It was an unusual configuration in that the server was both an Oauth client and server. See, in the setup above, the personis server could obviously be an Oauth server, in that H25n registers as a client and needs to authenticate against it with credentials for the user (as could the location and activity feeders). However, we don't want to handle passwords, so the personis server wants to delegate that to someone (E.G. Google), and so becomes an Oauth client as well. The user agent dance is similar to a normal Oauth login, but extended as H25n pass the user on to the personis server for authentication, who then passes them onto google as another layer of delegation. User provisioning was similarly extended. So when a new user goes to H25n, the flow might go:
- End user browses to H25n to view their lack of measurable health
- H25s bounces the browser to Personis asking to authenticate the user, and to know some user details (name, photo etc)
- Personis bounces the browser to google asking to authenticate the user, asking for the same userinfo items.
- Google does the login and permission screens (for access to the userinfo) and bounces the browser back to personis with an auth token
- Personis checks the received auth token with google via a backchannel. If it works and an access token is granted (and hence personis has permission for this) it asks for the userinfo as well and stores that. If the user has not been seen by personis before, a new user datastore is created. The user is then asked if they are OK for H25n to use this datastore. If so, the browser is then bounced back to H25n with an auth token.
- H25n checks the received auth token with personis via a backchannel and if it works pulls over the user info and off it goes with a newly provisioned user.
Simple
So that's where it stood. But now we have OpenID Connect, with it's fancy JWT tokens which can contain signed claims about the user, and apparently:
One thing that makes ID tokens useful is that fact that you can pass them around different components of your app. These components can use an ID token as a lightweight authentication mechanism authenticating the app and the user. link
So my question of the day is, can the new OpenID Connect tricks help clean up the above flow?
Well, in the Android world, there is a common pattern that looks a bit like this. You'd have an app that talks to a web service, that talks to google. So the Kombu.cha, the social app for hand-made sandal enthusiasts, will talk to it's associated web site, but might use Google for auth again. Here's how the kombu.cha app and site can use these tricks to access a google API (and incidentally how google solved the horrid user experience of Oauth on Android and iOS):
- At google, you set up a project, and in that get an Oauth client id for your web service, and another for your android app.
- The app uses GoogleAuthUtil to get an ID token from google using
it's client id, but the token has a wrinkle in that the it asks for an
`audience:server:client_id:
` scope as well, and thus includes your web service as a scope, and the 'azp' (authorised party I suppose) is the client ID of your app. This means that google has signed something saying that it thinks that the person to which the ID token relates has authorised correctly to google via the said app, and that they are happy for the app to access the web service. - When the app wants to use it's associated web service, it sends the token along with the request. The service then validates that the token is signed by google, has the correct audience and azp, and can take that as proof that the user is all good.
Can we use this to clean up the original Personis flow? It might look something like:
- H25n bounces the browser to google, with a scope that includes the client id for the personis service (which would need to be published somewhere).
- The user would be asked to OK something like 'I agree that HipsterFitnessVisualisat.ion can access data from PersonisServi.ce', and then bounces back to H25n with an access token.
- H25n can then use the access token to get an ID token, with appropriate audience and azp fields, and signature.
- H25n sends a request to personis that includes the ID token. After appropriate checks, personis knows that google believes that the user has authorised H25n to access personis (phew!). There is the small matter of how personis knows that it's H25n doing the requesting, but perhaps we can rely on them having the ID token in the first place (In fact you can't trust this in the android flow above either, because the device might be rooted).
And here's where we hit the limits of the spec. Although Google has made
the Android flow work, extending it to arbitrary web services is
off-piste. But let's give it a quick go at the Oauth2 playground. Create
a google project for personis and generate appropriate Oauth client IDs.
Enter our audience:server:client_id:<web app id>
Error, scope unknown.
So no.
(Originally a