The Desktop App Technical Workflow explained how our Code Sample uses OAuth messages. Next let’s look at the code behind the workflow.
We will summarize the coding goals for our Desktop Code Sample as follows:
- Solid code with a clean Separation of Concerns
- Reliable if End Users click Sign In more than once
- We want to control Usability aspects where possible
- Follow AppAuth-JS Standards so that we can easily take new releases
- Aim to write only a Small Amount of technically simple code
- Easy to Change to a Private URI Scheme later
We will continue with TypeScript, since it gives us a very productive coding language. Our page flow is similar to that of our SPA, except that we no longer need to handle login responses as part of the page workflow.
If you are coding in C# there is an Identity Model Code Sample here that uses the Loopback Interface:
Desktop ‘Authenticator’ Class
For our SPA we intentionally used an ‘authenticator‘ class to deal with the ‘authentication related stuff‘ of logins and tokens, and this is the main class that will need to change.
Other Custom Classes
I also created a number of other plumbing classes, in order to separate concerns, and we will cover the roles of some of these shortly.
Triggering a Login Prompt
As for our SPA, our Desktop App will trigger a login as a result of the UI not being able to call APIs, when there is no valid access token.
The HttpClient class and its 401 handling for making reliable API calls is precisely the same as for our SPA:
When we cannot get an access token we will throw a Login Required error that is handled specially:
Our error handler class will then move the user to the Sign In Required page, and the below router method records the user’s current hash location so that it can be restored after login.
The above mechanism copes with logins both at application startup and also when the user’s session expires:
- If a user logs into our desktop app on Monday, then goes home, then accesses the running app again on Tuesday morning, the user is prompted to sign in again but does not experience any problems
The Authorization Redirect
We use AppAuth-JS classes when redirecting the user to login. This involves starting a loopback web server, creating an authorization handler and starting the authorization redirect:
Note that AppAuth-JS is a fairly new library and at the time of writing we need to write more authentication plumbing than we did for our SPA.
Custom Authorization Handler
AppAuth-JS provides the following concrete class for managing authorization redirects via the loopback interface:
It is also possible to override the base AuthorizationRequestHandler class, which I chose to do because I wanted to customize the following areas:
- Behavior when the user clicks Sign In multiple times
- Behavior of the Browser Display after login has completed
I called my class BrowserAuthorizationRequestHandler and followed the same AppAuth-JS pattern as the default NodeBasedHandler.
The Authorization Response
Our authenticator class follows the AppAuth-JS pattern of using a notifier to inform us when the authorization response has been received:
The Authorization Code Grant
Handling the login response mostly just covers the Authorization Code Grant message, which is easily implemented via the AppAuth TokenRequest classes.
The response is of type TokenResponse, and contains the token fields that we want to store:
In my code I have referred to this as the ‘authState‘ in line with a class from AppAuth mobile libraries which we’ll be using shortly.
I followed the AppAuth-JS approach of using NodeJS events to signal login completion, which reduces code complexity.
The loopback web server raises an Authorization Response event to resume authorization processing, then consumes an Authorization Response Completed event to update the browser.
User Actions and Re-Entrancy
The main difficulty of coding Desktop Logins is that the browser is completely disconnected from the desktop app. We may have to handle user actions such as these:
- A busy Corporate User clicks Sign In
- The user accidentally closes the browser tab
- The user retries by clicking Sign In again
Some developers may not need to handle re-entrancy, but others will need to get their solutions past QA departments or fussy UX / Business owners.
Loopback Web Server
To handle re-entrancy our loopback web server class uses static class members and only starts if it is not already started:
We avoid running multiple web servers, since we want to ensure that after a successful login we have cleaned up and are not leaking any resources.
Correlation when Resuming Logins
Our PKCE handling uses a simple CodeVerifier class, which correctly generates a different random challenge + verifier for every login request:
To handle re-entrancy and ensure that we resume the correct login, with the correct contextual data, the loopback web server looks up the LoginEvents Instance from the authorization request.
If this is not done then we can run into issues such as the Code Verifier for Sign In Attempt 1 being used for Sign In Attempt 2, leading to end user problems that we’d prefer to avoid.
Login State Map
Login requests generate a random ‘state’ parameter that is also received in the response, and we can use this for correlation. Our LoginState class is a collection that maps the State to LoginEvents for each login attempt.
This is a little more complex than I’d like but gives us some extra reliability. I have kept this code separate to the main authentication processing, so that the Authenticator class focuses only on OAuth message handling.
Reliability is Not Perfect
It is probably logically impossible to solve all re-entrancy cases in a reliable manner. If the user clicks Sign In twice, then logs in twice, on the second attempt the web server will be stopped and the user will see this:
We will live with this, since the alternative is to keep a web server running indefinitely and leak resources. We’ve achieved our main goal of allowing users to retry, and we can explain the solution to QA and other stakeholders.
Login Handling is Complete
We have completed the difficult work and our Desktop App can now call APIs to get and render data, in precisely the same manner as for our SPA.
The Refresh Token Grant
After 30 minutes our access token will expire, and we will use the AppAuth TokenRequest classes for token renewal, then update our memory state:
The only interesting aspect is that we must check for a specific error code of ‘invalid_grant‘ in order to detect session expiry, at which point we just empty our tokens from memory.
Testing Token Expiry
Finally, our Authenticator class has some developer options to enable us to productively test token expiry.
We can’t force 30 minutes / 12 hours to pass to expire access / refresh tokens. However, we can cause OAuth messages to use expired behavior:
- Corrupted tokens get sent to the Authorization Server
- We get the same error responses as for real expired tokens
- We can verify that our application handles them correctly
Where Are We?
We have a pretty complete desktop sample and hopefully can release to production without any issues.
The re-entrancy code is more complex than I’d like, and is a consequence of OAuth standards, which dictate that we have to use two disconnected UIs.