OAuth2 and LabVIEW — Replacing the Web Server
Welcome to what will probably be the last update for this series of blog posts. In the very first article I wrote about this topic, I mentioned that I didn’t like using global variables to pass information from the web callback function to the main part of the application.
I’m finally going to address that design concern by completely changing how I handle the web callback. Credit for this approach goes to a couple of people: Jörg Hampel of Hampel Software Engineering (and the DSH Workshops) who first showed me this idea over Skype, and also Robert Smith, who wrote this excellent article about writing your own LabVIEW web server.
For this situation, I don’t need a full-blown web server–just something simple enough to receive the OAuth2 callback and parse the URI. So instead of depending on any of the three or four LabVIEW/NI web servers, I’m just going to use the TCP functions built into LabVIEW. I recommend you read the article linked above, but I’ll explain my approach here. I’ve updated the LabVIEW 2019 code in the Gitlab repo if you want to download it.
Basically, I create a TCP Listener and wait for a connection. This connection is going to come from the web browser where you authenticate with the OAuth2 web service. Recall that it uses a “redirect” callback to pass the authentication codes back to us.
Once I get the connection, I read the web request from the browser, which looks something like this:
GET /OAuth/Redirect?state=xxx&code=yyy&scope=profile%20openid%20https://www.googleapis.com/auth/userinfo.profile&authuser=2&prompt=consent HTTP/1.1 Host: 127.0.0.1:54605 Connection: keep-alive Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.135 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 Accept-Encoding: gzip, deflate, br Accept-Language: en-US,en;q=0.9
Most of that, you can ignore. The most relevant part is this:
GET /OAuth/Redirect?state=xxx&code=yyy HTTP/1.1
All of the parameters to the callback show up in the URI that’s between “GET” and “HTTP/1.1”. So, after reading the header, I just need to parse that string to create the “Query String Map”–which is the same data I had in the global variable in the original solution.
A few notes on the code:
- In the example here, I go ahead and read all of the headers and the request body, but I could get away with just reading the first line containing GET. If I ever need more headers or the body, I’ll already have code to handle it and it’ll be easy to modify this for future use cases.
- Once a request is received, the TCP Listener is shut down. This means this is a one-shot authentication. This is all we need, since our web browser is the only thing that knows about this HTTP server, and it just needs to work once.
- I have an arbitrary one second wait after writing a response back to the web browser and before I close the TCP connection. There’s probably a better way to handle this, but if I immediately close the connection, the web browser doesn’t have a chance to read the response. If that happens, the browser shows a “connection reset” error to the user.
Let’s talk about this design decision: Basically, I tried to balance reusability (reading all the headers and body) with simplicity (handling only one request, and having an arbitrary wait). Reading all the headers and body is something I will almost certainly need in the future. For now, I just put those in strings on the front panel. But since I don’t have a future use case in mind, I don’t know what the needs will be for handling multiple requests and more gracefully handling the response text. So any code I write for that part now will probably have to be rewritten when I know how I want to use it–so, I just took the easy way out until I know more.
Anyway, here’s the code for the HTTP Listener…
I create the TCP Listener in the parent VI and wire zero to the port input. I do this so that I can let the operating system find an available port to run the HTTP Server on. Once I have that port number, I can plug it into the callback URI for when I launch the web browser for authentication.
Also note that I run the HTTP Listener VI in parallel with the VI that launches the web browser (“Ask Permission.vi”), and then use “Merge Errors” to resynchronize afterwards. I want the listener listening before the web browser authenticates. I could also have put the “Ask Permission.vi” first, since it returns as soon as the web browser launches. But I want to be clear that it would deadlock if I did the opposite, and wired the Error Out on HTTP Listener directly to “Ask Permission.vi”.
That’s all there is to it. With these changes, I can get rid of my global variables and the web service in the LabVIEW Project. I no longer have to remember to start the web service when I run my app. It’s a simpler solution, which I think is more elegant.
Any comments on this approach or my code? Respond below!
Other articles about OAuth2 and LabVIEW…
- OAuth2 and LabVIEW — The Evolution of an Example
- OAuth2 and LabVIEW — Part Two, The Authentication Process
- OAuth2 and LabVIEW — Part Three, Improving the Example
- OAuth2 and LabVIEW — Part Four, Reusability