OAuth2 and LabVIEW — Part Two, The Authentication Process
This is part two of a
three four part blog post where I describe how to use OAuth2 with LabVIEW. See also:
- Part One — Setting up a LabVIEW Web Service Callback Function
- Part Three — Improving the Example
- Part Four — Reusability
In part one, we created a web service that the authentication process uses to call us back with an authentication code. Here in part two, we’ll write code to actually go through the authentication process and call an example Google web service.
Calling the Authentication Server
Okay, with the callback ready, we can now call the authentication server and request a code. Fortunately, Google has conveniently set up example servers and client codes to make testing with their endpoints easy. That’s what we’ll use for this example.
We will call https://accounts.google.com/o/OAuth2/v2/auth as our authorization endpoint. It takes several parameters that are part of the OAuth2 standard which we’ll pass with the URL:
- scope=”openid profile”
- redirect_uri=<our redirect endpoint>
- client_id=<client id registered in advance>
- code_challenge=<code challenge that’s part of the PKCE extension hashed with SHA256>
The “redirect_uri” is what we created in part one of this blog post. The “client_id” for our example belongs to an “AppAuth Example” which Google created for testing. The “state” is a random string used to discriminate between calls. “code_challenge” and “code_challenge_method” are part of the PKCE extension.
We’re also going to need some helper VIs…
For PKCE, we create a “code verifier” random string, and then encrypt it and pass it in “code_challege”. So we’ll need a helper VI to compute a SHA256 secure hash. I took the easy way out and just call a .Net SHA256 algorithm that’s built into Windows. This is almost the only platform-dependent part of my code. (See “SHA256.vi” for how I invoked .Net.)
Note that “state” and “code_challenge” are binary data. We will need a way to reencode the binary data so it can be correctly transmitted as part of the URL. For this, I modified an example from Christian Loew that I found on NI’s website for “base64 encoding”. It was relatively straightforward to make a “Util Convert to Base64url No Padding.vi”, which is what’s needed here. Base64url is an encoding that ensures a URL-friendly character set.
I also created a subVI which creates a random string, which is used to create a random “state”. It’s also used for the PKCE code verifier.
Finally, I need to launch a web browser. I can’t just make a GET or POST HTTP request from the diagram; I need a browser so that I can enter my username and password information.
For this, I used “System Exec” that’s part of vi.lib. To get it to work right, I used
cmd /c "" "<url>"
because that’s apparently what you are supposed to do. (And that’s another platform-dependency in this code.)
My “AskPermission.vi” constructs the URL with all the parameters above and invokes the browser. Here’s what happens when the VI runs…
We now have the first part of our main program. We initialize the globals to empty, ask the user for permission, and then wait for a response or an error. Make sense?
This is also where I compare the “state” that I sent in my browser call to the “state” that I got from the callback. In this case, since there’s only one call that should be pending to my web service, I just check for equality. If I had multiple calls active, I could use this to differentiate them.
Turning the Authorization Code into an Authorization Token
Okay, so now we have an authorization code, which confirms that the authorization server knows who the “resource owner” is, since I had to enter my credentials and agree. I need to give this code to my app (the “client”) so that it can exchange the code for a token. The app calls another endpoint (the “token” endpoint) with my code and its own “secret” to get the token. If the code and secret look okay, everything is fully authenticated, and the server will respond back with an “access token”. I’ll pass this token as a parameter to the rest of the web service API.
Warning: This token is powerful, since it’s all anyone needs to access your data. For this reason, the token (usually) expires in a short amount of time. Perhaps in a future blog post, I’ll write about strategies for refreshing the token, but one solution to an expired token is to just ask the user to give permission again.
The PerformCodeExchange.vi is pretty straightforward—send a POST message with…
- code=<code returned in the callback>
- redirect_uri=<same redirect path as above, but it’s unused with this grant type>
- client_id=<same as the one sent above>
- client_secret=<the secret password that goes with my client_id>
- code_verifier=<the unencrypted verifier that was sent encrypted as a code_challenge above>
Recall in authorization call, we passed a “code challenge” that was the encrypted version of a “code verifier”. In the token callback, we pass the unencrypted “code verifier”. The server will encrypt this with SHA256 and ensure it matches the “code challenge”. This verifies that the entity requesting the token is the same entity that requested the authorization code.
This token endpoint returns a JSON structure which we have to parse. Unfortunately, the JSON parser built into LabVIEW is pretty limited, and assumes you know exactly what JSON you’re going to get. I went ahead and used it for this example, but I think it should be replaced with something more flexible. For example, if an error occurs, the error is typically reported in JSON that’s got different entries than when the call succeeds.
This is going to come up again when we make our actual API call later.
Question #3 for Readers:
What’s your favorite JSON parser for use in LabVIEW and why? (In part three, I will show one example of a different JSON parser.)
I use the JSON parser to turn the response into a Map, so that makes it easy to lookup anything in the response with the “Look in Map” function in LabVIEW. In my case, I need the “access_token”, so I can turn around and call Google’s “userinfo” endpoint…
The “userinfo” call returns a few things about me (or whoever authenticated in the initial web browser call), such as my full name and my photo. If you look back at the browser permission screen, it tells me exactly what it is going to share.
I wanted to pull out the “picture” field, which is a URL to an image. I then wrote a VI to turn that into something I could display.
Following the URL to get the image is fairly straightforward. Parsing the image, though, is another issue. There is a Windows API call somewhere which can parse a variety of image formats, but I didn’t want to introduce yet-another-Windows-dependency into my app. So instead, I read the “Content-Type” header from the HTTP GET response, and use a case structure to handle PNG and JPEG formats. (And I could add other parsers, as needed.)
Here’s what the diagram looks like for retrieving the image and converting it to a LabVIEW image, which can then be converted to into a format that the 2D Picture Control understands. It’s using a handy VI that converts a string containing a PNG into the right format.
Unfortunately, there’s not an equivalent VI for JPEGs. But there is a VI for reading from a JPEG file instead of a string. The VI is in vi.lib and is password-protected, so I couldn’t find a convenient way to get at its implementation. I once again did the expedient thing and wrote the string to a file, then used the Read JPEG File.vi function to convert it.
Question #4 for Readers:
Have any of you found a good solution to parsing and displaying different image types in LabVIEW? What’s your favorite?
I also didn’t find a great way to resize images, so I just did without for this example. Here, you can see the upper corner of my bald head.
Question #5 for readers:
What’s your favorite solution for programmatically resizing images, or performing other image manipulations?
There’s one other thing I wanted to point out. Notice that I wrote my own VI for parsing HTTP headers. That’s because the built-in version work–a VI called “GetHeader.vi”—didn’t behave the way I expected it to. The code below just returns an empty string, even though the GET returns a non-empty string for the headers.
Question #6 for readers:
What am I misunderstanding here? How am I incorrectly using GetHeader.vi (from LabVIEWHTTPClient.lvlib)?
My guess is that GetHeader.vi only looks at the REQUEST header, and not the RESPONSE headers. I didn’t see that in the documentation, though. At best, it’s ambiguous and confusing.
My “Parse GET Headers.vi” just parses the string coming back from the GET to put the headers in a Map, and then I look in the Map to find the Content-Type. I’m just not sure what I was doing wrong, so once again, I chose the expedient approach.
Okay, we have a VI that works for calling an authenticated web service, but now we need to improve upon it. That’s coming up in part three. Thanks for reading this far! I’m interested to hear from you about the questions I’ve posed–please comment below.