Authenticating with Graph API Using a Device Code
Introduction
Recently, I’ve been wanting to use PowerShell Core more often with Graph API. But what has held me back was having to use WinForms or WPF to display the Microsoft login page to authenticate the user. Searching around, it appears you can authenticate Azure AD users with a device code too - https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-device-code
By doing this, your script/tool/app/device can generate a device code to be entered on another device (that has a web-browser). For example, I can run a PowerShell Core script on a headless Ubuntu server which provides a code, then use that code to sign in from my Windows PC.
Create Azure AD Application
As with anything Azure AD auth related, you will need an Azure AD application with the relevant permissions assigned to it:
- Login to https://portal.azure.com
- Under Azure AD > App Registrations, create a New registration (you could also modify an existing one if you’d like). Enter a Name, Supported Account Type and leave Redirect URI blank:
- On the App Overview, take a note of the Client ID and Tenant ID as this will be needed:
- Under Authentication, assign a Redirect URI and change Public Client to Yes (this allows device code support):
- Finally, under Permissions, assign whatever Graph API delegated permissions your application requires. You can Grant Consent for all users or have it prompt for each user:
Azure AD Device Code Authentication Flow
I will outline below the high-level steps that need to be taken to use device code authentication:
-
A request to https://login.microsoftonline.com/tenantId/oauth2/devicecode is sent containing the client_id, resource (https://graph.microsoft.com/) and scope (Graph API permissions separated by a space):
-
A response should hopefully be returned with a device_code, user_code and a URL to enter the user_code:
{ "user_code": "CMD2YRRFS", "device_code": "CAQABAAEAAAAP0wLlqdLVToOpA4kwzSnxMpoyNohkJtMNWWgulAIFVCDDsE-awXUanQ5HUM45rF_nQ98zS0xEyb9TvDVK3LLdSMph1aO_0cqAY19UohgtZrv0UMPvtqsp5o4yKHzodr4chdVOknfTiFn1x8V8zx4mxlp4DyEts_TAtnqoBKm0UHlEoTKy60gcxGwY0pJ4GMIgAA", "verification_url": "https://microsoft.com/devicelogin", "expires_in": "900", "interval": "5", "message": "To sign in, use a web browser to open the page https://microsoft.com/devicelogin and enter the code CMD2YRRFS to authenticate." }
-
The user would then browse to https://microsoft.com/devicelogin and enter the user_code:
-
Once the user_code has been entered and the user signs in, you can then successfully request a OAuth token at https://login.microsoftonline.com/tenantId/oauth2/token by submitting the code (device_code from step 1), client_id and grant_type (urn:ietf:params:oauth:grant-type:device_code). In a real world example this would be looping until the user_code is entered:
-
An access_token (OAuth token) should be returned:
{ "token_type": "Bearer", "scope": "Group.Read.All User.Read", "expires_in": "3599", "ext_expires_in": "3599", "expires_on": "1569231721", "not_before": "1569227821", "resource": "https://graph.microsoft.com", "access_token": "eyJ0eXAiOiJKV1QiLCJub25jZSI6IjQ1Yk1GSk4wMjZXdWVYNWdaVnVJVDlkUXZrR2I0a1M1alA0VW45ZFpJbDgiLCJhbGciOiJSUzI1NiIsIng1dCI6ImllX3FXQ1hoWHh0MXpJRXN1NGM3YWNRVkduNCIsImtpZCI6ImllX3FXQ1hoWHh0MXpJRXN1NGM3YWNRVkduNCJ9.eyJhdWQiOiJodHRwczovL2dyYXBoLm1pY3Jvc29mdC5jb20iLCJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC85MWE5M2NmZi00YzE4LTRjODMtODVlMi00MjBjZTQ2NGMxMDMvIiwiaWF0IjoxNTY5MjI3ODIxLCJuYmYiOjE1NjkyMjc4MjEsImV4cCI6MTU2OTIzMTcyMSwiYWNjdCI6MCwiYWNyIjoiMSIsImFpbyI6IkFWUUFxLzhNQUFBQUhkcW1IQnZkN1AwY2I0OVNNQVZDTU0xY3NzTE1KNlVKaFloNkU4T3o2L2txSDlZTHp6bDRnOHI1UHVza2FIVkpURjhERElLWTZhamRZckdnTFR5eU0zVzBuZFVTRXRKOTU2YTArQzJwNGZBPSIsImFtciI6WyJwd2QiLCJtZmEiXSwiYXBwX2Rpc3BsYXluYW1lIjoiR3JhcGggQVBJIERldmljZSBDb2RlIiwiYXBwaWQiOiJlODkyYTQ3YS05Nzg0LTQyNWYtOGZlNS03YTYxNGE2ZmFlNDMiLCJhcHBpZGFjciI6IjAiLCJmYW1pbHlfbmFtZSI6IkZvcmQiLCJnaXZlbl9uYW1lIjoiTGVlIiwiaXBhZGRyIjoiOTQuMTc0LjkwLjEzMyIsIm5hbWUiOiJMZWUgRm9yZCIsIm9pZCI6IjllYjNkMWRjLTJiZjYtNGFlMS1iZjk4LTExMmM5Y2ZhOWM5NyIsInBsYXRmIjoiMTQiLCJwdWlkIjoiMTAwMzNGRkZBRjBBM0Q3QyIsInNjcCI6Ikdyb3VwLlJlYWQuQWxsIFVzZXIuUmVhZCIsInNpZ25pbl9zdGF0ZSI6WyJrbXNpIl0sInN1YiI6ImhudUgxVE1xc0FWUXRNYVhfQXRmWU9LSmJDT1E4M0dYSENBZTNBekJVbm8iLCJ0aWQiOiI5MWE5M2NmZi00YzE4LTRjODMtODVlMi00MjBjZTQ2NGMxMDMiLCJ1bmlxdWVfbmFtZSI6ImxlZS5mb3JkQGxlZS1mb3JkLmNvLnVrIiwidXBuIjoibGVlLmZvcmRAbGVlLWZvcmQuY28udWsiLCJ1dGkiOiJ0MEhWV2EzRnVraWNheUpGMll3eEFBIiwidmVyIjoiMS4wIiwid2lkcyI6WyI2MmU5MDM5NC02OWY1LTQyMzctOTE5MC0wMTIxNzcxNDVlMTAiXSwieG1zX3RjZHQiOjE1MzkzMzE3OTl9.HVAAyTaOWvntqWrVhIkacux852m0aGl-RmEyTVhgW418Z8nPgDHTUOzX78VcRocq3FQ4QAN3CHlqfBtCn6MeMYsFFrRKYjv8yb-A0_P0tjgRmk_mQGHcmA-oy2Y4zJVtEdedH-dJKYLLC9NpBFAixzi0zltLE6Rj8ZWlbwpVS13iJHfJ10wJqS2ZXYTGWXZqY8qeC1GYTyacOSGsYcggHmy_mRGqXvlOP-Bb9ATMSKpG6D3g3XmFhRi_n4y-zW7AikiUh_XxzTm3FMNTQ7LBVfNjIgTcwBmPqnrCauZpe9Difqa8g9tR4UOMsgqVdKEufmLf9uZeaKLOS8MqnBpYpg", "refresh_token": "AQABAAAAAAAP0wLlqdLVToOpA4kwzSnxlKvjy2eiQlmv1R_0pLYH019toMdFcM4itQEzvqZGSTlsmKbA81NbDaNjvCZN7klEeaMV1j-O7MyM1i3ypYZzaEr6xiWu8tyZH48PdpP9wEuv2JzPS6FIjm-nqKonlGYN8m0RLhoxEzZp2EGzZhyZhiKGCSh4l1hNTBIS_AHWDseL8UvIs9b3mmYEV8GjLO-EnzAmp8e08f7t7Rx6knIE4jR-IKxvfly909_Y3sc1VzgPCg6JLr8ut1mieaCym4R_7iSejDlolo3LU0f15uCiwFYX20cSZX5aUL7QnDYdEZ4pFV7lp8p6LwMvHCS3XUu2KRAKjrUJT_bpci7_HM8oK4S7PmS3aduGlemXLMVRl9hFrle0gq9R4UuPrUEI3aaPbai-8ydTiKkfPQ7jCef5Mg6Jjc0tAvwgw0b3zG9eTZO-RYBjphj4rFr4_bjy1VyiIXcY9nqnLhOBYWH5EQlZgZDjgYzFvfi7oVKHTnd_YE00TQrXKntCtTxWpstQSKBg5BvCZSmsSZVK4ZBqi2YmqVpGNmm-LLA14pOXtsjwuMto4s2GqvyKFG4rVwU0fjZbjQcjVQU0W06XBFNEVc8aUrr09iNEPLphhMoLR93dzENxJ4O3MbgAkS_C8ncDzSTo42qkYOwjeSWcIvQ6-W4SPPqC0MDocuVhcD1AYnPRUm3OXjwaxuJBuEiGl95Rfhf9nT8gU3LybmNNpG2T-KvV-I7KUgu3m756Ide2-0bPoePfBHpfrPAxl3qpWa_6FwtcNY0y3KiFcgZPSctGtrVxlR4lj2xyCx09rowLjdPsQAgsaizZuBPEPWJgwxXB6mevd2mVqi1kJ2ADOlYFT0dv1ASQZEP8mi391sH6zi9E_217Ho-DDsnToGUTwTGnIj7R_EMknLsKK4eCSM1LOmmiSmGK_7FoSgvtoYF_Bm5mBaggAA", "id_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0.eyJhdWQiOiJlODkyYTQ3YS05Nzg0LTQyNWYtOGZlNS03YTYxNGE2ZmFlNDMiLCJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC85MWE5M2NmZi00YzE4LTRjODMtODVlMi00MjBjZTQ2NGMxMDMvIiwiaWF0IjoxNTY5MjI3ODIxLCJuYmYiOjE1NjkyMjc4MjEsImV4cCI6MTU2OTIzMTcyMSwiYW1yIjpbInB3ZCIsIm1mYSJdLCJmYW1pbHlfbmFtZSI6IkZvcmQiLCJnaXZlbl9uYW1lIjoiTGVlIiwiaXBhZGRyIjoiOTQuMTc0LjkwLjEzMyIsIm5hbWUiOiJMZWUgRm9yZCIsIm9pZCI6IjllYjNkMWRjLTJiZjYtNGFlMS1iZjk4LTExMmM5Y2ZhOWM5NyIsInN1YiI6Imt2ODFlYUtwZmR4QW1Xd2Faa3FoREZCemloT0FEMFhBNVROWHlHcHdvSWsiLCJ0aWQiOiI5MWE5M2NmZi00YzE4LTRjODMtODVlMi00MjBjZTQ2NGMxMDMiLCJ1bmlxdWVfbmFtZSI6ImxlZS5mb3JkQGxlZS1mb3JkLmNvLnVrIiwidXBuIjoibGVlLmZvcmRAbGVlLWZvcmQuY28udWsiLCJ2ZXIiOiIxLjAifQ." }
-
You can then use the access_token with any Graph API request in the “Authorization Bearer” header.
Example Script
Here is an example script in PowerShell (Core and Windows) that will prompt the user to sign in a web browser with a code. The script will loop until an OAuth access token is then granted (once user signs in):
For more information on how to then use the access_token in PowerShell, I recommend you read my post https://www.lee-ford.co.uk/getting-started-with-microsoft-graph-with-powershell/