Getting an access token for AzureAD using PowerShell and device login flow
Intro
Have you ever wanted to query an API that uses access tokens from Azure Active Directory (AzureAD) from a PowerShell script?
There are a lot of solutions for this that uses an application in AzureAD and authenticates using its client-id and secret. If I have a web application or a non-interactive service this is the way to go. My friend and colleague Emanuel Palm wrote a great post on Microsoft Graph API with PowerShell for that scenario.
In this post, I’m going to cover how to get an access token from AzureAD using the OAuth 2.0 device authorization grant flow. This flow is great when I want my script to be run interactively with a user present. It relies on the access rights of the user and I don’t need to save any application secret in the script. This also makes sure that the script won’t perform any action the user doesn’t have access to perform.
This technique will give the user a similar experience to using Connect-AzAccount on PowerShell where the user is asked the following question:
To sign in, use a web browser to open the page https://microsoft.com/devicelogin and enter the code ARX2JKHNC to authenticate.
I’ll also cover a bit of a “hack” to simplify this flow for an interactive user so the user doesn’t have to copy and paste the code to a browser, but it relies on creating and opening a temporary HTML-file in the default browser by calling Start-Process.
Starting a device login flow
To start a login flow using the device authorization grant flow we need to call the REST method POST to an URI that looks like this:
$TenantID can either be the ID of a specific tenant or be set to a well known generic setting, like ‘common’. Setting the TenantID to ‘common’ will use the tenant where the user was created. If I want to sign in to a tenant where I am invited as a guest, I’ll need to supply the TenantID.
You can read more about the available options for TenantID in the documentation on Application configuration options.
When calling the REST method I need to supply the two parameters client_id and resource in the body.
The client id is the id of an application that has delegated permissions to perform what I want my script to do. In this case, I’m going to list a few groups and for that, I’m going to borrow the client id of the application Azure PowerShell that is used by the Azure PowerShell module.
The resource defines where my access token will be valid. In my case, I’m going to use the token to access Microsoft Graph API so I will use “https://graph.microsoft.com/" as my resource.
Now let’s try this in PowerShell:
This will start a new sign-in process and give me a response containing a message that I print to the console with instructions to the user. The response also contains the properties user_code and device_code that we will use later on.
The sign-in experience
I’ve now shown a message to the user that instructs them to browse to https://microsoft.com/devicelogin and enter a code on a page that looks like this:
After entering the code, the user will be asked to sign in to my application, in this case, “Microsoft Azure PowerShell”.
Once signed in, the user will get a confirmation message instructing them to close the browser.
Getting the token
Once the user has completed the sign-in process, my script will need to get the access token back. This is done by calling the REST API once again, this time using the token endpoint. Here is an example:
If all went well, I’ll now have my access token in the property access_token of the response.
Get groups from AzureAD using Microsoft Graph API
To use our token to authenticate to Microsoft Graph API, we need to use a header called Authorization and give it the value of “Bearer " followed by our token.
Here is an example of getting the top 1 group from my tenant using Graph API:
And that’s it! But wait, there is more!
Improving the experience
Using the device code flow works great for any script being run by an interactive user, but I think the user experience is a little bit clunky. I would prefer to skip the first three steps of opening a browser, navigating to https://microsoft.com/devicelogin and entering the device code.
Let’s see what I can do about that!
After some browser debugging, I’ve figured out that if I use the REST method POST and supply the code in a parameter called “otc” to the URI https://login.microsoftonline.com/common/oauth2/deviceauth I will bypass the steps where the user has to enter the code. However, opening a browser and instruct it fo POST a message to a site wasn’t as easy as I first hoped. My solution is to create some HTML code, drop it in a temporary file on disk and then open that file, hoping that it will open in the user’s default browser. My HTML code contains a form with the parameter “otc” and a small javascript that submits the form using a POST request. You can try this by running the full function below using the parameter “-Interactive”.
A complete function
I have of course combined this code in a function ready to be used. Give it a spin and let me know what you think!