I the previous post, we created a new Azure AD Application registration, gave it permissions to Microsoft Graph. We also added an OAuth prompt to the main dialog and made it possible for the user to log in both in the emulator and Microsoft Teams.
In this post, we will add some helpers and enable the user to call the Microsoft Graph. We will create an example using both the Microsoft Graph JavaScript Client Library and PnPjs v2 for hooking into the Azure Bot Service auth flow. We will add some additional validation logic into the Owner Resolver Dialog
, and also add a similar Alias Resolver Dialog
.
Bot Framework in Node.js | Complimentary post |
---|---|
Let’s begin (Part 1) | Bot Framework 4 ARM template Deploy with Key Vault |
Microsoft Teams (Part 2) | |
Dialogs (Part 3) | |
Interruptions (Part 4) | |
Auth and Microsoft Graph (Part 5) | Azure AD & Microsoft Graph permission scopes, with Azure CLI |
Azure AD & Microsoft Graph OAuth Connection, with Azure CLI | |
Calling Microsoft Graph (Part 6) |
Here is the link to the Github repository for this post: https://github.com/simonagren/simon-blog-bot-v6
In the previous post, we saw that the OAuthPrompt
gives us a token after the user logs in. We will not store the token locally (which I explained more in the last post), instead call the OAuth prompt again whenever we need a token.
Normally you would have to add MSAL
or ADAL
settings that the GraphClient
or PnPjs
would use to fetch a token, and then use to call Microsoft Graph.
In the Bot, we already have a token and we will work with that in the clients.
This is a high-level visualization of how the Bot is built:
We will install these packages:
npm i @microsoft/microsoft-graph-client
npm i @microsoft/microsoft-graph-types --save-dev
npm i @pnp/graph-commonjs @pnp/nodejs-commonjs
In the src
folder we create an additional folder helpers
. This will in our case contain 3 new files:
simple-graph-client
and simple-pnpjs-client
to call Microsoft Graph. GraphClient
from Microsoft, with the token from the Bot. It contains methods to call the Microsoft Graph.PnPjs
graph client from Patterns And Practices (PnP)
, with the token from the Bot. It contains methods to call the Microsoft Graph.We also add another dialog in the form of:
The graph helper imports simple-graph-client
and simple-pnpjs-client
, and contains two methods: userExists()
and aliasExists
.
The methods will be used in the Owner resolver dialog and Alias Resolver Dialog. Both methods want a TokenResponse
and a string
.
This method instantiates a new client
using the SimpleGraphClient
based in the Microsoft Graph SDK, by sending in the token. Then we run the userExists()
with the string input, to see if the user (owner) exists in the tenant.
public static async userExists(tokenResponse: any, emailAddress: string): Promise<boolean> {if (!tokenResponse) {throw new Error('GraphHelper.userExists(): `tokenResponse` cannot be undefined.');}const client = new SimpleGraphClient(tokenResponse.token);return await client.userExists(emailAddress);}
This method instantiates a new client
using the SimplePnPJsClient
based on PnPjs V2, by sending in the token. Then we run the aliasExists()
with the string input, to see if the group alias is already taken in the tenant.
public static async aliasExists(tokenResponse: any, alias: string): Promise<boolean> {if (!tokenResponse) {throw new Error('GraphHelper.aliasExists(): `tokenResponse` cannot be undefined.');}const client = new SimplePnPJsClient(tokenResponse.token);return await client.aliasExists(alias);}
In this case, we first import the Client
and the User
type.
import { Client } from '@microsoft/microsoft-graph-client';import { User } from '@microsoft/microsoft-graph-types';
Then we initialize the Client
with the token that was injected.
constructor(token: any) {if (!token || !token.trim()) {throw new Error('SimpleGraphClient: Invalid token received.');}this.token = token;this.graphClient = Client.init({authProvider: (done) => {done(null, this.token);}});}
Then we call the Microsoft Graph to see if the user exists.
public async userExists(emailAddress: string): Promise<boolean> {if (!emailAddress || !emailAddress.trim()) {throw new Error('SimpleGraphClient.userExists(): Invalid `emailAddress` parameter received.');}try {const user: User = await this.graphClient.api(`/users/${emailAddress}`).get();return user ? true : false;} catch (error) {return false;}}
We import the Microsoft Graph types for Group
, graph
from the PnPjs Graph package (with an alias) and also BearerTokenFetchClient
from the PnPjs Nodejs package.
If you have seen some more of my posts I usually use the AdalTokenFetchClient
, where you supply the clientId
and clientSecret
of your Azure AD application. But this time we already have a token so the BearerTokenFetchClient
is perfect in this scenario.
import { Group } from '@microsoft/microsoft-graph-types';import { graph as graphClient } from '@pnp/graph-commonjs';import { BearerTokenFetchClient } from '@pnp/nodejs-commonjs';
Then we initialize the graphClient
with the token that was injected.
constructor(token: any) {if (!token || !token.trim()) {throw new Error('SimpleGraphClient: Invalid token received.');}this.token = token;graphClient.setup({graph: {fetchClientFactory: () => {return new BearerTokenFetchClient(this.token);}}});}
Then we call the Microsoft Graph to see if the alias exists.
public async aliasExists(alias: string): Promise<boolean> {if (!alias || !alias.trim()) {throw new Error('SimplePnPjsClient.aliasExists(): Invalid `alias` parameter received.');}try {const group: Group[] = await graphClient.groups.filter(`mailNickname eq '${alias}' or displayName eq '${alias}'`)();return group.length > 0;} catch (error) {return false;}}
Just as in the previous blog post, we have added an OAuthPrompt
to these dialogs, and an additional promptStep
. This is because we need the token now to call Microsoft Graph.
Check out that post if you need more details.
The promptStep
only kicks off the new login prompt.
private async promptStep(stepContext: WaterfallStepContext): Promise<DialogTurnResult> {return await stepContext.beginDialog(OAUTH_PROMPT);}
Then the initialStep
has been changed to collect the token from stepcontext.result
. And if we didn’t get a token, the login wasn’t successful.
And these parts are the same in both ownerResolverDialog
and aliasResolverDialog
.
In the ownerPromptValidator
we have added some additional validation to make sure, not only that the email is correctly formatted, but also a valid existing user.
Here we are using the GraphHelper
to see if the user exists. Once again, we send in the tokenResponse and the email address the use wrote to see if it exists in the tenant. And this one is using Microsoft Graph Client
.
if (!await GraphHelper.userExists(OwnerResolverDialog.tokenResponse, owner)) {promptContext.context.sendActivity('User doesn\'t exist.');return false;}
Here we have added just one validation to make sure that the alias doesn’t already exist. Here the GraphHelper
is used again and this one is utilizing PnPjs
.
if (await GraphHelper.aliasExists(AliasResolverDialog.tokenResponse, alias)) {promptContext.context.sendActivity('Alias already exist.');return false;}
In this post, we have looked at how we could call Microsoft Graph in two different ways. And how to incorporate Microsoft Graph into our prompt validation.
In the next post, we will get a bit fancy using Adaptive Cards in our prompts.