Note: The links to the GitHub repositories for each of the libraries discussed in this post are available at the end under “MSAL Links”

Background Information

I had the good fortune of spending the summer as a software engineering intern at Microsoft. To be specific, I worked as a part of the Azure Identity Developer Experience team that is in charge of developing and maintaining Microsoft’s authentication libraries for JavaScript, or the Identity DevEx JS team for short.

If you’re unfamiliar with DevEx, my extended team, you’ll be delighted to find out that the libraries we build for a variety of different platforms are all open source and their source code can be found in the AzureAD GitHub repo. Whether or not you have any experience in the development of OSS (open-source software), I strongly recommend you take a crack at contributing to any of our libraries through opening up an issue or even submitting a pull request.

Motivation

What we do at DevEx

The DevEx JS team is in charge of two JavaScript libraries. These are the Active Directory Authentication Library (ADAL) and Microsoft Authentication Library (MSAL) for JS. Nowadays, we spend most of our time working on MSAL, which is the newer one of the two. Both libraries solve pretty much the same use case: facilitating the acquisition, storage and management of OAuth 2.0 access tokens to make authorized requests to Microsoft online resources, such as Microsoft Graph.

At the beginning of my internship, my manager and I set out to pick an internship project that would allow me to deliver the largest possible impact in the short 12 weeks that my internship would last. Since I come from a heavy Ruby background, I thought about proposing I spend my internship writing an implementation of MSAL for Ruby. After all, Ruby developers deserve efficient and more secure authentication for their Azure apps as well.

What we needed: Implicit v.s. Auth Code

Before I had a chance to propose said project, my manager introduced me to some of the challenges and areas of opportunity our team would face in the not-so-far future. The challenge that stood out was the possibility of extending MSAL JS from only implementing the OAuth 2.0 Implicit flow to a version that also offers the Authorization Code flow.

Without getting too technical on the differences between the two, the fact is that security-wise, the Implicit flow is optimized for single page applications (SPAs) in which the code that does the authenticating is executed in the browser. This means it is designed to be used by applications built on frameworks such as Angular, React and Vue (among many others). The following is a diagram with a rough overview of the Implicit Grant, in the context of Azure Active Directory, as described by the OAuth 2.0 specification.

Implicit Grant Type Diagram
Implicit Grant Type

The fact that the Implicit Grant is optimized for in-browser authentication for SPAs also means it is not recommended for server-side or native (desktop or mobile) applications, even if they’re developed using JavaScript. If we were to make MSAL for JS implement the Auth Code grant in addition to the Implicit grant, we would be one step closer to making MSAL for JS framework agnostic.

At the time my internship started, we’d never had an official JavaScript implementation of the Auth Code grant. This meant that migrating MSAL for JS to Auth Code would imply stepping into unknown territory. Before I brought up my idea for an MSAL Ruby gem, my manager proposed that I develop a proof-of-concept MSAL for Electron which implemented the Authorization Code (Auth Code) Grant.

For context, Electron is a JavaScript framework that allows developers to write cross-platform desktop applications using web technologies (i.e. JavaScript and its many frameworks). In an Electron app’s main process, developers have access to the Node API. In the renderer process, they can choose between any and none of the existing frameworks (again, Angular, React, etc.).

Since Electron applications are packaged into desktop apps, they qualify as native public clients in the context of OAuth 2.0. For the same reason, Electron is a great target for a proof of concept exercise in implementing the Auth Code grant in a JS library. An Electron library would, by default, not be “framework agnostic”, but given that roughly 90% of the code I ended up writing is Electron independent, it is fair to say it’s not that far from what we’re looking for. The following is another rough overview, this time of the Authorization Code Grant in the context of an Electron application using Azure Active Directory for authentication.

Authorization Code Grant Type Diagram
Authorization Code Grant Type

In the context of a SWE intern working for the JS SDK team, I could not have asked for a better mission. MSAL for Electron, as a 12 week project developed by a single intern, will obviously not replace MSAL JS. What it actually accomplishes is shedding a great amount of light on what the challenges, obstacles, intricacies and even solutions to implementing the Auth Code grant in JS/Node look like.

Philosophically speaking, the main motivation for my MSAL for Ruby proposal was not actually working in Ruby. The real motivation for me was being allowed to write an entire authentication library from scratch. Not only that, but the fact that the library would carry the sort of weight you can only get, short of being a famous developer, while working at a company like Microsoft.

I immediately and excitedly accepted my assignment and we started discussing the scope and requirements in a little more detail. The following is what we agreed would be a reasonable scope for a proof-of-concept MSAL for Electron that could be fit into a 12-week intern project:

Functional Requirements

MSAL for Electron should:

  • Expose a method with which developers can acquire OAuth 2.0 Access Tokens
  • Implement the OAuth 2.0 Authorization Code Grant for access token acquisition

Non-functional requirements

MSAL for Electron should:

  • Be a ‘plug-and-play’ NPM package that developers can add to an existing Electron app
  • Be designed in a way that minimizes the amount of code the developer needs to add to their Electron app
  • Have an automated testing suite that covers a reasonable amount of the library’s code
  • Look as close to other MSALs as possible from the developers’ point of view (i.e. expose the same API surface)
  • Have comprehensive usage documentation
  • Have a working sample available to developers for context and as a demo

As is evident, the real challenges of the exercise lied in the non-functional aspect of the requirements. After all, value is rarely added through a large feature set. It usually comes mostly from the user’s or developer’s experience with a specific tool set. There’s reason my team is called “Developer Experience”.

First Step: Low-Fi Exploratory Demo

As soon as we agreed on a set of requirements, I had the feeling many developers get when they start a project:

I want to write the code and I want to write it right now.

As we all know:

This is, of course, an ironic phrase used to emphasize the actual, opposite reality.

A few hours of planning and thoughtful design can really save weeks of coding. I would have liked to sit down and design the implementation for the MSAL Electron PoC library, but since neither I nor anyone I knew had any experience with implementing Auth Code grant in JavaScript, I wouldn’t have been able to validate any design I came up with.

What I chose to do was a two-sided approach. On one side, I decided to create a direct prototype or demo of an Electron application that implements the Auth Code grant flow without the use of an external library. On the other side, I decided to dive deep into the source code of both MSAL for JS and MSAL for .NET.

The Demo

In order to fully understand the challenges, requirements and details of implementing the Auth Code flow in the context of a JavaScript (specifically Electron) application, I went and manually implemented the Auth Code flow in a lightweight Electron application. You can try it out yourself and look at the code on my GitHub repo electron-auth-code-demo. The demo, which I developed while I was researching our existing MSAL libraries for inspiration for the design, took me about four weeks (a third of my internship).

The reason it took me four weeks to write a simple demo? I had to learn TypeScript, Electron, the entire specification for the OAuth 2.0 Auth Code Grant (including security add-ons such as PKCE), as well as the request-response formats for Azure AD’s two main endpoints (authorization and token). If I’d gone straight to design without familiarizing myself with all of these tools and technologies that were new to me, It would’ve taken a lot longer and the results would have been of a considerably lower quality.

The reason I thought it was a good time investment is that by the time the demo was finished, I was sure I could actually implement MSAL for Electron successfully. Not only that, but I had, through this research exercise, already written most of the code. Having done this exercise while being fully aware I wasn’t jumping into developing my actual internship project without previous design work, I was better suited to actually design MSAL for Electron because I had already found, understood, and solved the entire set of challenges and obstacles for the problem.

The Deep Dive into Existing MSALs

I looked at MSAL for JS to get a feel of what the JS architectural patterns my team likes to follow actually look like in the context of authentication. I looked at MSAL for .NET because we have an actual implementation of the Auth Code grant in that library and the general architecture it uses is better suited for my particular use case.

Since I wrote the library in TypeScript, the syntax and style ended up being the perfectly balanced hybrid between what I found in our JS and .NET libraries. The architecture was heavily inspired by MSAL .NET which was written to implement all the OAuth 2.0 grant types in an extensible way. MSAL for JS on the other hand is written specifically to support the Implicit flow.

Once I had a clear idea of the architecture and approach I’d follow to create the MSAL for Electron proof of concept, I started coding it out.

Developing MSAL for Electron: Challenges and Solutions

After having provided some insight into the motivation, reasoning and design behind MSAL Electron, I can summarize what the library does the following way:

MSAL for Electron allows applications to authenticate users and acquire OAuth 2.0 access tokens in a secure and efficient way.

That’s it, nothing more and nothing less.

It may sound rather simple and direct, but the reason a project like this takes a lot of time and effort to build is two-fold. First, it must be executed in such a way that the underlying code is easy to maintain, extend and understand. Second, a few significant challenges are bound to come up, no matter how well you try to design around them.

The first challenge was precisely coming up with a good design having no existing implementation of the Auth Code grant as a JS library to use as an example. Since I’ve already discussed the way that challenge was overcome, I’ll go straight to the second challenge, which took the most time and creative thinking to work around.

Custom File Protocols

I would like to make it clear that although I spent a long time researching this specific problem, I am in no way a security expert. Please, correct me if I’m wrong and take everything that follows with a grain of salt.

When implementing the Auth Code Grant type, Authorization servers that want to comply with OAuth 2.0 (such as those Azure AD provides) must return an Authorization Code as a URL fragment in a redirect URI provided by the application being authenticated. This means applications must be able to listen for 302 redirect requests and responses at an address that developers can configure themselves. Usually and specially in JavaScript, this means starting an HTTP server, setting the hostname and port, and handling the response in a callback.

The OAuth 2.0 for Native Apps spec recommends against using localhost for redirection. Instead, they suggest using the loopback address (127.0.0.1):

While redirect URIs using localhost (i.e., “http://localhost:{port}/{path}”) function similarly to loopback IP redirects described in Section 7.3, the use of localhost is NOT RECOMMENDED.

RFC 8252 - OAuth 2.0 for Native Apps

As many developers know, localhost resolves to 127.0.0.1, so they are functionally the same. Why make the distinction? It turns out there are a few advantages to using 127.0.0.1 directly:

  • It prevents the app from inadvertently listening on network interfaces other than the loopback interface (so we don’t listen on eth0, wlan0 or others instead of lo0).
  • Less susceptible to client-side firewalls (they are more likely to blacklist localhost than 127.0.0.1)
  • Less susceptible to misconfigured host name resolution on user devices (they are more likely to mess with localhost than 127.0.0.1)

My team and I found that this suggestion is as good as it gets when listening on an HTTP server for redirects. Short of having developers build separate web services where they securely listen for HTTP redirects (which would break the requirement of the library being ‘plug-and-play’), it seemed there was no way we could do it that would be more secure.

Then, with a lot of research and help from some of my teammates and mentors in DevEx, I came across two different elements of the Electron API that yielded a better solution. The first was a simple event listener that allowed MSAL for Electron to handle 302 redirects asynchronously. This was the BrowserWindow.webContents.on('will-redirect') event.

At first I thought that with that event, the Electron BrowserWindow would be able to receive redirect URI responses without the need of an HTTP server listening for them. I tried to use it on it’s own, but I soon realized that the listener event depended on a listening server to be triggered and actually receive the authorization code in the redirect URI. At that point, I stepped back, uncommented the HTTP server listener section of the code, and more or less gave up on trying to increase the security with which we handled the auth code.

And then I ran into custom file protocols/schemes. Most operating systems (if not all), have a scheme called the “file” protocol. This allows applications to create, modify and access files that are stored in the user’s machine. Just as HTTP (http://) is a protocol for locating resources through the world wide web, the file (file://) protocol is used to locate resources in a local system. This meant if I could register a custom file protocol for the Electron application using MSAL for Electron, I could avoid HTTP servers altogether, not worrying about malicious actors listening in on localhost or 127.0.0.1.

It turns out Electron’s API has a module called protocol that allows you to register and un-register custom file schemes that your Electron application can listen on. Using the protocol module, I was able to give developers the ability to define their custom file scheme URL (i.e. msal://, technicalboy://, myapp://) in their MSAL for Electron configuration, allowing MSAL to then listen on that address and stop listening as soon as the auth code is retrieved in the redirect flow.

The same RFC about OAuth 2.0 for Native Apps mentions the possibility of using custom file schemes to listen for 302 redirects, but since it explicitly recommends the loopback interface (127.0.0.1), I didn’t realize it could be a better, more secure option until I looked deep into the use case. So why is it better than localhost/loopback?

Since the browser is a highly-restricted “sandbox”, malicious client web applications have very little potential to cause harm. Most importantly, browsers prevent two separate web applications running in a single browser session to interact with each other in malicious way. If you were running a malicious app in browser tab “A” and were to receive a redirect URL at the loopback address in browser tab “B”, the malicious app in tab “A” would not be able to snoop in and steal an authorization code from tab “B”, even if it knew tab “B” was listening on the loopback address. Even if you removed the Auth Code step and went straight for requesting the access token, the browser offers a decent amount of protection. This is why the Implicit flow actually works for in-browser applications.

The same is not true in a desktop application that has access to operating system resources. If you were to execute a malicious Electron app on your machine, and said app knew you were listening for auth code redirects at https://localhost:3000, they could easily intercept the auth code and get access tokens that were intended for your trusted application. Of course, if the malicious application knows what custom scheme you are using, they can still steal your auth codes and access tokens, but it is a lot harder to guess technicalboy://authorization than https://localhost:3000.

Last thoughts

I am very glad to report that the most important goals that were set for my internship project were successfully accomplished. Now that we have a working, high-quality proof of concept MSAL for Electron, we have a lot more freedom and confidence moving forward. Although MSAL for JS will keep it’s implicit flow approach for the near future, there are some important conversations going on about either adding Auth Code to MSAL for JS or, possibly, replacing MSAL JS core with MSAL for Node.

The truth is MSAL for Electron as a proof of concept was meant to kill to birds with one stone.

First, we acquired a deep understanding of what the implications and actual code for a JS (TypeScript if I’m being accurate) implementation of the Authorization Code grant are. At the same time, it turns out that MSAL for Electron is actually a super-set of what MSAL for Node would be, so if we so desired, we could extract all of the Node code out of MSAL for Electron and have a huge head-start on MSAL for Node.

If we do end up building MSAL for Node, we could ship a framework-agnostic MSAL for JS and then write simple wrappers that would be framework specific (i.e. msal-electron, msal-react, msal-angular). This way, any general updates would be done on MSAL for Node and each framework-specific wrapper would be updated in isolation to adapt to the changes and demands of the dev communities for each of those frameworks.

It is to soon to tell which direction we’ll end up going with, but I’m very excited to see how my internship project helps inform these decisions and what impact it has in future efforts by the DevEx JS team.


Do you have any opinion with regards to what I’ve discussed, or would you like to suggest a topic for my next blog post? Write it down in a comment below or write me on Twitter @technicalboy__ or e-mail. Don’t forget to share this blog post on Facebook, Twitter or LinkedIn using the buttons below. If you like the blog, recommend it to your friends!

Comments