Colin's Journal: A place for thoughts about politics, software, and daily life.
Owlauth allows users to register a device (e.g. a phone) and then use this device to confirm their identify when logging into a website or application. For this to work the Owlauth app running on the phone needs to be able to receive push notifications of authentication requests. There are many centralised ways to push notifications to a phone, but as each domain owner can run their own Owlauth server, a centralised solution isn’t a good fit.
By taking advantage of Go’s cheap handling of concurrent processing with Goroutines, a simple HTTP based push approach can be used. Clients connect to the server:
The server returns before the Keep Alive timeout, which means that the client will reuse the same TCP (and SSL) session as for the first request. This makes this timeout and GET operation an effective ping that proves the connection is still established. If the TCP connection becomes invalid, the client’s HTTP library will open a new connection to the server, giving the desired reconnect behaviour.
The server implementation in Go is straightforward:
var requestedEtag string requestedEtag = r.Header.Get("If-None-Match") authChannel := reqStore.RegisterListener(dev.DeviceOwner) defer reqStore.UnregisterListener(dev.DeviceOwner, authChannel) clientGone := w.(http.CloseNotifier).CloseNotify() var request *reqdb.PendingRequests timeout := time.NewTimer(DeviceRequestGetTimeout) for { select { case req := <-authChannel: if req.GetEtag() != requestedEtag { request = &req // Return the ETag w.Header().Add("ETag", req.GetEtag()) return request, nil } // Just loop and try again. case <-clientGone: // Nothing to be done - just return log.Printf("Device gone - returning\n") w.Header().Add("ETag", requestedEtag) return nil, nil case <-timeout.C: log.Printf("Device request get timeout reached.\n") w.Header().Add("ETag", requestedEtag) return nil, nil } }
The reqStore object keeps track of outstanding authorisation requests and provides the details on a channel to all registered clients that are sat waiting in this GET.
The only other element of the equation on the client side is adopting a suitable retry strategy when http connections are not working. For desktops that could be just a simple back-off to a few seconds sleep. For the Android client it needs to taken into account the current network state to avoid excessive battery drain.
I’ve now got a basic version of this working on my phone. I’ll run it for a while and see how much battery impact it has.
The full list of my published Software
Email: colin at owlfish.com