4 min read

Authorization Code Flow

Authorization Code Flow
Photo by Arnold Francisca / Unsplash

Introduction

In the past, I was always curious about the workings of Connect-AzAccount, the authentication command from the Az.Accounts PowerShell module. This led me to delve into debugging, and the subsequent article is a product of that exploration. It's intriguing that both Az CLI and Az PowerShell are operational across all tenants, even the newly created ones. I aimed to emulate this functionality in PowerShell and utilize it in my scripts. For instance, this could be beneficial when executing commands across various tenants, a task that the Az modules are not adept at handling.

Authentication flows

Before beginning the debugging and disassembly of the commands, it's essential to discuss the authentication flows, particularly those utilized by Connect-AzAccount. Much of this information is available on Microsoft Learn, for example over here or here, but it's all very convoluted in my opinion.This article will concentrate on the Authorization Code Flow. Subsequent articles will cover Client Secret, Client Certificate flows, Federated Credential, and Managed Identity.

Components

Initially, let's briefly examine the components that facilitate this functionality. There are two main components: an enterprise application, also known as a service principal, which may exist across multiple tenants or within a single tenant. Additionally, there is an app registration that is confined to a single "home" tenant.

The app registration:

  • Defines the client secret / certificate / federated credential
  • Defines the API permissions the enterprise app requires (delegated or application)
  • Defines the token claims (properties)
  • Exists in the home tenant

The enterprise app:

  • Defines the permissions for this tenant
  • Defines the allowed users / groups
  • Exists in each tenant which implements the application

Powershell Enterprise App

Now we reach the core of the story: the authentication cmdlets function across all tenants because the app registration resides with Microsoft, and a service principal is automatically created for each tenant upon a user's first login via the corresponding PowerShell module.

The client ID for the PowerShell modules is "14d82eec-204b-4c2f-b7e8-296a70dab67e," which remains consistent for all users. However, the enterprise application possesses a distinct object ID within each tenant.

Flow Details

As you can see in the above picture, which I shamelessly stole from the OAuth docs, the process exists in 2 big steps:

  • A call to the /authorize endpoint to retrieve an authorization code + use hashed
  • A call to the /token endpoint to retrieve an access token and optionally refresh token depending on the scope requested

Mind you this is an interactive authentication flow requiring a user to authenticate and authorize!

Code Explanation

I have crafted a script that illustrates the operation of the Authorization Code Flow using exclusively Powershell 7, with a slight incorporation of .NET. The script is divided into three functions, each detailed below:

  • AuthorizationCodeRetrieval: Initiates the default browser with the authorization endpoint and scopes, with an option to logout immediately afterward. It also displays chat-GPT generated JavaScript and HTML. Additionally, it creates the PKCE to safeguard against MITM attacks. The code verifier is produced and hashed using SHA256. This hashed value, the code challenge, is used when requesting the authorization code. In the subsequent step, the code verifier's unhashed value is needed to exchange the authorization code for an access token or refresh token. Upon calling the authorization endpoint, an asynchronous HTTP listener on port 8400 must be opened (for the Powershell App Registration managed by Microsoft). Once the listener is active, it waits for user authentication and the return of the authorization code to our script, which is then forwarded to the next function.
  • AccessTokenRetrieval: Converts the previously obtained authorization code into an access token, which is then used to access our resources. It also requires the code verifier's unhashed value from the prior function to finalize the PKCE process. This function yields the access token and, if applicable, the refresh token (which necessitates the offline_access scope).
  • PowershellInteractiveLogin: This function acts as the connector for the aforementioned functions, establishing default parameters and orchestrating the call of both functions while managing the transfer of inputs and outputs.

Code

Some troubles I came across when trying to create this were:

  • Documentation on the Authorization Code flow is abundant, yet it appears that MSAL is the preferred choice for many (which is not a negative aspect, but understanding it necessitates additional information).
  • Getting the details on the PKCE requirements was a pain to find out (you might start to notice a pattern).
  • I have almost no knowledge of Javascript / HTML / ... (Thanks to Robbe for modifying / maintaining the site 😉), but Chat GPT / Gemini / ... do!
  • Noticing that I should use an async listener, otherwise the user is not able to stop the process when you don't get correctly authenticated. (except when killing the Powershell process).
		
Code loading...
      	
    
Show on Github

Conclusion

I'm thrilled with the script I've created, as it has enabled me to gain a deeper understanding of the commands "Connect-AzAccount", "Connect-MgGraph", and what MSAL fundamentally does behind the scenes. I hope others trying to decipher this process won't have to endure the same challenges I faced! May you enjoy applying this knowledge in your own scripts. 😁