Azure Active Directory Authentication using OAuth 2.0 Device Code

Overview

In this post we’ll be covering how we can leverage Azure Active Directory for authenticating users during a conversation with a chatbot. As the most simple use case, we’ll be requesting the user’s first and last name through the Graph API. In this example we’ll focus on Node.js-based bots running on Azure Bot Service. For this, we’ll be using Device Code Flow with OAuth 2.0.
Edit May 29, 2018:
Good news! OAuth and AAD authentication is coming natively to the Bot Framework, as recently announced at Microsoft Build 2018 (Video see here).

Why Authentication?

Many use cases for bots do not necessarily require to know who they are talking to, therefore not requiring authentication. However, especially productivity bots often need to rely on authentication for leveraging external APIs and other services on behalf of the user.
While Azure Active Directory (AAD) enables us to integrate our bot into existing Active Directory domains, we can also leverage AAD B2C for authenticating users via:
  • Facebook, Google, Amazon, LinkedIn, Twitter, and GitHub accounts
  • Weibo, QQ, and WeChat accounts
  • … and last but not least: Microsoft (Live) accounts
In this post, we will be using the v2.0 endpoint of Azure Active Directory. While v2.0 has not full feature parity with v1.0 (see this comparison), one big benefit is that v2.0 enables our bot to be completely account-agnostic. This is obviously a compelling feature, but it is noteworthy that some applications might need to keep relying on v1.0.
We’ve already covered building an intelligent chatbot on Azure Bot Service using Node.js in one of the last posts, so let’s directly dive into it.

Device Code Flow with Azure Active Directory

Before we go into the server-side code of the bot, let’s briefly walk through the four steps of the device code flow authentication:
  1. Request a device code from AAD and present the user the login URL
  2. Query the status until the user signed in
  3. Request a new access token (in case it is expired)
  4. Query the user’s information (name, email, etc.)
In the following subsections, we’ll be using the following two IDs:
AZUREAD_APP_ID - The App Id of our application in the Application Registration Portal
DIRECTORY_ID - Our Azure Active Directory ID
Our Azure Active Directory ID
Our Azure Active Directory ID
We can retrieve the DIRECTORY_ID by going to the Azure Portal, switching to the correct Azure Active Directory and clicking Properties –> Directory ID. We’ll look into how you get your AZUREAD_APP_ID in the sections below.

Step 1 – Requesting a device code

First, we request a device code for the user by calling the /oauth2/devicecode URL. In this case we reference our AZUREAD_APP_ID (the ID we’ll generate for our application later) and also a resource, in this case access the Graph API:
var options =  
  url: `https://login.microsoftonline.com/${DIRECTORY_ID}/oauth2/devicecode`,
  method: 'POST',
  json: true,
  headers:  
    'Content-Type':'application/x-www-form-urlencoded'
  },
  form:  
    'resource':'https://graph.microsoft.com',
    'client_id':AZUREAD_APP_ID
  }
}
This returns the following response:
 
  "user_code":"FW37K...",
  "device_code":"FAQABAAEAAAD...",
  "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 FW37K... to authenticate."
}
We will need to remember the following three values:
var code = body.user_code; // presented to the user
var deviceCode = body.device_code; // internal code to identify the user
var url = body.verification_url; // URL the user needs to visit & paste in the code
Now we’ll have the user open the url in a browser window, paste in the code and authenticate with his or her account. For presenting the user a sign-in option in the chat, we can use the SigninCard from the Bot Framework:
let card = new builder.SigninCard(session)
                      .text(`Sign-in with code: ${code}`)
                      .button('Sign-in', url)
session.send(new builder.Message(session).addAttachment(card));

Step 2 – Querying the authentication status

As a second step, we will need to query the status of the authentication, i.e. we need to check if the user actually visited the website and authenticated:
var options = 
  url:`https://login.microsoftonline.com/${DIRECTORY_ID}/oauth2/token`,
  method:'POST',
  json:true,
  headers: 
    'Content-Type':'application/x-www-form-urlencoded'
  },
  form: 
    'grant_type':'device_code',
    'resource':'https://graph.microsoft.com',
    'code':deviceCode,
    'client_id':AZUREAD_APP_ID
  }
}
This will return either a 400 error (in case the user hasn’t authenticated yet):
 
  "error":"authorization_pending",
  "error_description":"AADSTS70016: Pending end-user authorization. ...",
  "error_codes": 
    70016
  ],
  "timestamp":"2018-05-14 13:46:33Z",
  "trace_id":"2972860e-....",
  "correlation_id":"d1be47b4-..."
}
Or, once the user is authenticated, a wonderful 200 response:
 
  "token_type":"Bearer",
  "scope":"User.Read",
  "expires_in":"3599",
  "ext_expires_in":"0",
  "expires_on":"1526309286",
  "not_before":"1526305386",
  "resource":"https://graph.microsoft.com",
  "access_token":"eyJ0eXAiOiJKV1QiLCJu...",
  "refresh_token":"AQABAAAAAAD....",
  "id_token":"eyJ0eXAiOiJKV1QiLCJ..."
}
We can see that the access_token expires after one hour and that it cannot be revoked. In contrast, the refresh_token lives until it is revoked on the server-side of our bot. More details can be found in this article.
Either way, we’ll need to remember at least the following two fields:
var access_token = body.access_token; // used to authenticate API calls on behalf of the user
var refresh_token = body.refresh_token; // used to request a new access_token
Additionally, we could also remember expires_on for knowing when we need to leverage the refresh_token for getting a new access_token (See next step).

Step 3 – Requesting a new Access Token

We could now use the access token and continue with step 4, however, this token will expire after one hour. Therefore, we can use the following HTTP call to request a new access token by passing in the refresh token:
var options = 
  url:`https://login.microsoftonline.com/${DIRECTORY_ID}/oauth2/token`,
  method:'POST',
  headers:headers,
  json:true,
  form: 
    'scope':'User.Read',
    'resource':'https://graph.microsoft.com',
    'refresh_token':refresh_token,
    'grant_type':'refresh_token',
    'client_id':AZUREAD_APP_ID
  }
}
We’ll get a similar response back:
 
  "token_type":"Bearer",
  "scope":"User.Read",
  "expires_in":"3599",
  "ext_expires_in":"0",
  "expires_on":"1526309675",
  "not_before":"1526305775",
  "resource":"https://graph.microsoft.com",
  "access_token":"eyJ0eXAiOiJ...",
  "refresh_token":"AQABA..."
}
As we already have the refresh token, we just need to remember the new access_token (and optionally the new expiration time):
var access_token = body.access_token;

Querying the user’s information

Since we requested permission to the Graph API, we can now move forward and request the user’s information:
var options = 
  url:`https://graph.microsoft.com/v1.0/me`,
  method:'GET',
  json:true,
  headers: 
    'Authorization':`Bearer ${access_token}`
  }
}
Finally, we get the user’s information back:
 
  "@odata.context":"https://graph.microsoft.com/v1.0/$metadata#users/$entity",
  "id":"xxxxxxxxx",
  "businessPhones":[],
  "displayName":"xxxxx",
  "givenName":"Clemens",
  "jobTitle":null,
  "mail":null,
  "mobilePhone":null,
  "officeLocation":null,
  "preferredLanguage":null,
  "surname":"Siebler",
  "userPrincipalName":"xxxxxxxxxx"
}
Lastly, we just grab the information we need and e.g., welcome the user:
var id = body.id;
var firstName = body.givenName;
var lastname = body.surname;
As we now understand the basic flow of Device Code Flow authentication, let’s see what we need to do in order to embed this in our bot.

Setting up Azure Active Directory

In most cases, we will already have an existing Azure Active Directory (AAD) service running in Azure that we can leverage. However, if we do not want to interfere with our production AAD, we can setup a separate, new Azure AD for testing:
  1. Hit the + sign in the Azure Portal to create a new resource
  2. Search for Azure Active Directory B2C and hit create
  3. Create a new tenant
  4. Enter the organization’s name and an initial domain name (will be in form of something.onmicrosoft.com)
  5. Follow the remaining instructions and create a few test users
One thing needs to be noted:
For easier testing of the AAD integration, we should rely on using a private browsing window and just logging into our AAD account within the new organization. In Azure Portal, we can click the little book symbol on the upper right for switching between the different directories.

Registration of our Bot in the Application Portal

As we’re using the v2.0 endpoint of AAD, we will need to register our bot through the Application Registration Portal. We need to make sure to log in to the portal with the appropriate AAD user, otherwise we might add our bot the wrong AAD (this happened to me and it took me quite a while to figure out why it wasn’t working). As described before, using a private browsing window makes this a lot less error-prone.
Application Registration
Application Registration
Adding a new application through the Application Registration Portal is fairly straight forward:
  1. Open Application Registration Portal and click Add an app
  2. Next, we’ll give our app a name
  3. Click on the new app and note the Application Id (this will become your AZUREAD_APP_ID)
  4. Under Platforms, click Add Platform and choose Native Application
In the following code, we will need your AZUREAD_APP_ID. Now we can move to the server-side and implement this in our bot.

Full Bot Node.js Example

The full example of how to plug all components together can be found in this repository: bot-service-device-flow-authentication. The most important parts of the code are in deviceflow.js.
Example Bot
Example Bot
It needs to be said, that this code does not handle the asynchronous responses for the HTTP calls as the goal here is to illustrate the individual steps required for authentication.

Summary

In this post we’ve showed how we can add authentication to a Node.js-based chatbot. We’ve walked through how to use Azure Active Directory (AAD) for authenticating users via either their domain user or by using their Microsoft, Google, Facebook, Twitter, etc. accounts. In both cases, we relied on the Application Registration Portal to register our bot as an account-agnostic native application in AAD. In our Node.js code, we then requested a device code, forward the user to the login website, and queried the status until the user has signed in. This whole process relied on the OAuth 2.0 Device Code Flow. The full example code can be found in the bot-service-device-flow-authentication repository.
If you have any questions, feel free to reach out to me on Twitter @clemenssiebler.
A big thank you goes out to Joonas Westlin who wrote a helpful post about How Device Code Flow works in Azure AD.

Leave a Reply

Your email address will not be published. Required fields are marked *