This is the second part of a series of posts where I try to find out how the authentication of the Java Client using refresh tokens works. Click here for part 1.
After finding out how the configuration actually works, the journey takes us back to the constructor of the AVSApp class.
After assigning the designConfig attribute, it creates a controller attribute by instantiating an AVSController object. It takes a lot of parameters. First of all the AVSApp instance (referenced by the this keyword) and several other objects, some of which are instantiated themselves in the constructor call. To be able to understand this better, we have to take a closer look at the AVSController constructor.
The parameter in the constructor is a listen handler of type ExpectSpeechListener. The main class passes itself, and that’s perfectly possible as it implements the ExpectSpeechListener interface.
The second parameter is an AVSAudioPlayerFactor. No idea what it is yet, but I suspect it will be responsible for playing Alexa’s responses.
An AlertManagerFactor is the third parameter. If presume this has something to do with Alexa’s functionality to start and fire alerts (like “set a timer for 1 minute”).
AVSClientFactory is the fourth parameter. We’ll have to get back to that later.
The fifth parameter is the DialogRequestIdAuthority. Probably something to do with the fact that Alexa can expect an answer to a question she asks herself, thereby starting a conversation with the human being.
A flag stating if the wake word is enabled is the sixth parameter, along with a WakeWordIPCFactory as the seventh. This last one undoubtebly listens for a signal that the wake word has been said. It uses a mechanism called IPC to do this. We’ll have to get back to that later on.
Lastly there’s a WakeWordDetectedHandler. It will probably take care of what needs to happen once the wake word has been detected.
Right now we’re not really interested in the ins and outs of the AVSController, as it’s very clear that it is a very important class in the entire setup. Right now we’re looking for the authentication, and there’s no sign here that this is being taken care of here. Time to get back to the main class.
These are the next set of statements in the constructor of the main class :
This looks like something interesting. A new instance of the AuthSetup class is being created. It is passed the configuration, and the AVSApp itself. Looking at the constructor of the AuthSetup class we see the following :
The parameters are a DeviceConfig, as expected and a RegCodeDisplayHandler. As the AVSApp implements the interface with the same name, this is allowed. All it does is set some local variables, so back to the main class. The next statement is addAccessTokenListener along with a parameter this, which is the main class itself. Again, the main class adheres to the interface, so no problem.
The method does nothing more than add the access token to a set of AccesTokenListeners that is instantiated when the instance of AuthSetup is created.
The main class constructor does this a second time by adding the controller as an AccessTokenListener.
Lastly it calls the startProvisioningThread() method. Let’s take a look at that one.
This method needs some studying. The first thing to be distinguished is that it does a different logic depending on the provisioning method configured in the device config. See my last post on this topic for more info on how this is obtained.
The second option, the companion service, is the way it works right now, so that’s not what we want. We can’t start the Java Client headless, so let’s concentrate on the first option.
The first thing this logic does is create an OAuth2ClientForPkce object called oAuthClient. It is instantiated with an URL obtained by the getLwaURL() method. This URL is the URL to connect to when using the “Login With Amazon” method.
Next an CompanionAppAuthManager called authManager using the deviceConfig, the client it just created and an instance of something called CodeChallengeWorkFlow. The last parameter is the AuthSetup object instance itself.
After that it creates a registrationServer of type CompanionAppProvisioningServer. It creates a new thread and immediately overrides the run() method of it to start the server. If you’re unfamiliar with multithreaded applications, please read up on it. This is an important concept which will come in handy trying to understand what’s going on here. Essentially, the registration server will keep on going, even if there is no interaction from a human. That’s exactly what it will have to do if it wants to refresh the token every once and a while.
Let’s concentrate on the OAuth2ClientForPkce class. The first thing you need to know is that the OAuth2 protocol is a well known procedure for setting up a secure connection between 2 applications over the internet. You can read all about it here. The Pkce suffix is the Proof Key Code Exchange concept, explained here.
The comment on top of the OAuth2ClientForPkce class looks exactly what we want.
The constructor of the class only sets a local variable called tokenEndPoint, so the hard work is probably done somewhere else.
Let’s take a look at what the CompanionAppAuthManager does in its constructor.
That isn’t too much. It sets some local variables to the parameters passed along in the constructor, one of which is an accesTokenListener, which is the calling class implementing the AccessTokenListener interface.
In essence, what it does is start a timer with a task. It does this without a delay, meaning it will effectively start a new thread executing the task RefreshTokenTimerTask. Let’s look at this one.
It’s a class inheriting from the TimerTask class of course (or it wouldn’t be allowed to pass as a parameter to the schedule method of a timer). The overridden run() method.
What it does, is try and call a method called refreshTokens(). It has a retry mechanism in the while loop, retrying for a number of times, defined a constant. If it succeeds it breaks from the while loop and finishes the run, but if it fails some reason, the while loop keeps on going and it tries again. Notice how there is a sleep interval between retries to possibly recover from a internet downtime for example. We could adapt the constants, or make them variable from a configuration file in the future for example. The hard work here is done by the refreshTokens method. Let’s take a look at that one.
This is quite straight-forward. It gets a refresh token and a client id from the configuration and called another refreshTokens overload using those 2 variables as a parameter.
Aah. The refresh token. According to me, the key to everything. If you read the documentation well, you will learn that the refresh token is something that is requested only once. Once it’s obtained, the client (being our Raspberry Pi) is responsible for storing the token because this is the one that is to be used to authenticate every time.
Time to go on a quest searching where the token is obtained and stored. Something to look forward to in the next post.