Building a CLI app with the Device Code grant, golang, and FusionAuth
By: Matt Gowie
Tags: Fusionauth, Golang, Oauth, Cobra, Cli

Now you’re probably saying to yourself “Oooh I get it now…”, but let me break it down just in case:

We’ve got our CLI example app: getgif . It has 3 primary commands: login , fetch , and logout The login command is responsible for authenticating our user using the FA go client library. We'll dive deep into this command shortly, so keep reading. The fetch command is responsible for fetching cool gifs from giphy, but only if the user is authenticated. The message is clear: authenticate and be rewarded with gifs. 😎 The logout command is responsible for logging our user out. Simple enough!

Awesome — next up let’s talk about our login command as that is going to cover the most important part of our example application: Authentication using the Device Flow.

Golang login Walkthrough

First, let’s talk about the steps we need to accomplish for our Device Flow authentication:

We need to reach out to the FA server to start the Device Code Flow and fetch a User Code for our user to input. We need to display our user’s User Code, open the browser for them to FA’s ‘Device code login’ page, and then start polling the FA server for when the user has completed the login flow. Once the user has completed the login flow, our polling of the FA server should respond back with a JWT Access Token. We can then trade that Access Token with the FA server to get information about our user and save that to disk as successful validation that our user is logged in.

Sounds great — let’s dive into each of those steps!

Starting the Device Code Flow

To start the Device Flow off, we need to send the “Device Authorization Request”. To show how we’ll do that, let’s start off with some abridged code and then break it down:

Here you can see the following:

We have our login command, where our main logic is in our Run function. Our Run func starts off by creating a FusionAuthClient and assigning it to faClient . This is using FA's go-client library, which will be our primary interface to our FA server going forward and is going to come in handy throughout our Device Flow. Next, we call faClient.RetrieveOpenIdConfiguration which returns to us an openIDConfig object of type OpenIdConfiguration. This will be handy as we'll be able to get some important OAuth endpoints from that configuration. Lastly, we call startDeviceGrantFlow with openIDConfig.DeviceAuthorizationEndpoint . This function sends the "Device Authorization Request" we mentioned earlier and populates the response into the result object which is of type fusionauth.DeviceResponse .

Now that we’ve sent our Device Auth request and we’ve got back a successful response, we’ve properly kicked off our FA server to accept a Device Login and have received our User and Device Codes locally to our CLI App. Let’s do something with those shall we?

Display the User Code, Open the Device Login page, and Poll the FA server for completion

Alright, now that we’ve got a User Code we need to show that code to the user so they can complete the “Device login”, we need to open the “Device login” page so the user doesn’t need to enter the URL themselves, and we need to start polling the FA server with our Device Code so we’re informed when the user completes their login.

Again, let’s look at some code and then work our way through it:

This should look familiar! We start with our LoginCmd’s Run function again, but we continue that function a bit more to do the following:

We call informUserAndOpenBrowser with our deviceResp.UserCode that we got from the FA server. This prints to the user's CLI the given userCode and open's up FA's /oauth2/device endpoint for our Application and Tenant. Next, we call startPolling which continually polls the given openIDConfig.TokenEndpoint every retryInterval seconds (5 seconds is RFC8628's / FA's default) to see if the user has completed the Device login (tied to the given deviceCode ). Once the user has completed their login, the FA server responds with a proper 200 response including a JWT Access Token which is then populated into our result object and returned.

Now we’re getting somewhere! The only thing left to do is trade our Access Token for the actual user record and save it to disk. Let’s do it!

Trade our Access Token for info on our User and write it to disk

Our last task is simple: We have a JWT Access Token and our handy FusionAuthClient has a method RetrieveUserUsingJWT , so we just need to combine the two and save the result. Let's see it in action:

Great stuff, we send off our JWT to FA, FA’s go-client intelligently responds with a UserResponse object, we save that response's User field ( resp.User ) to /tmp/getgif.json , and then we print a success message. Simple, easy, and effective!

Working!