JavaScript and Microsoft Graph

Neil HaddleyMay 28, 2022

A console application that calls the Microsoft Graph API

This post is based on Microsoft's "Build JavaScript apps with Microsoft Graph" tutorial.

The node console application I created here uses OAuth for identity management.

An OAuth application requires an "identity provider".

In this case the identity provider is Azure Active Directory.

I navigated to Azure Portal and selected Azure Active Directory

I navigated to Azure Portal and selected Azure Active Directory

I selected the "App registrations" link

I selected the "App registrations" link

I selected the "New registration" link

I selected the "New registration" link

I entered the name of the console application and selected "Register"

I entered the name of the console application and selected "Register"

I made a note of the "Application (client) ID" and the "Directory (tenant) ID"

I made a note of the "Application (client) ID" and the "Directory (tenant) ID"

I created a minimal node project

I created a minimal node project

I added @azure/identity, @microsoft/microsoft-graph-client, isomorphic-fetch and readline-sync dependencies

I added @azure/identity, @microsoft/microsoft-graph-client, isomorphic-fetch and readline-sync dependencies

I created an appSettings.js file including the "Application (client) ID" and the "Directory (tenant) ID" values provided in the Application registration page. I included the required Microsoft Graph permissions (the "graphUserScopes").

I created an appSettings.js file including the "Application (client) ID" and the "Directory (tenant) ID" values provided in the Application registration page. I included the required Microsoft Graph permissions (the "graphUserScopes").

I ran the console application

I ran the console application

Key moves

The index.js code contains the console application user interface. This is where the user selects an option.

The graphHelper.js file contains the most interesting code.

The graphHelper code creates a TokenCredentialAuthenticationProvider object and uses that to create a _userClient (client) object.

The client object is then used to make calls as shown in the Graph Explorer "Code snippets" tab.

GetUserSnippet from the application

GetUserSnippet from the application

Graph Explorer Code snippet

Graph Explorer Code snippet

Full code

The final code is included below.

index.js

TEXT
1// Copyright (c) Microsoft Corporation. All rights reserved.
2// Licensed under the MIT license.
3
4// <ProgramSnippet>
5const readline = require('readline-sync');
6
7const settings = require('./appSettings');
8const graphHelper = require('./graphHelper');
9
10async function main() {
11  console.log('JavaScript Graph Tutorial');
12
13  let choice = 0;
14
15  // Initialize Graph
16  initializeGraph(settings);
17
18  // Greet the user by name
19  await greetUserAsync();
20
21  const choices = [
22    'Display access token',
23    'List my inbox',
24    'Send mail',
25    'List users (requires app-only)',
26    'Make a Graph call'
27  ];
28
29  while (choice != -1) {
30    choice = readline.keyInSelect(choices, 'Select an option', { cancel: 'Exit' });
31
32    switch (choice) {
33      case -1:
34        // Exit
35        console.log('Goodbye...');
36        break;
37      case 0:
38        // Display access token
39        await displayAccessTokenAsync();
40        break;
41      case 1:
42        // List emails from user's inbox
43        await listInboxAsync();
44        break;
45      case 2:
46        // Send an email message
47        await sendMailAsync();
48        break;
49      case 3:
50        // List users
51        await listUsersAsync();
52        break;
53      case 4:
54        // Run any Graph code
55        await makeGraphCallAsync();
56        break;
57      default:
58        console.log('Invalid choice! Please try again.');
59    }
60  }
61}
62
63main();
64// </ProgramSnippet>
65
66// <InitializeGraphSnippet>
67function initializeGraph(settings) {
68  graphHelper.initializeGraphForUserAuth(settings, (info) => {
69    // Display the device code message to
70    // the user. This tells them
71    // where to go to sign in and provides the
72    // code to use.
73    console.log(info.message);
74  });
75}
76// </InitializeGraphSnippet>
77
78// <GreetUserSnippet>
79async function greetUserAsync() {
80  try {
81    const user = await graphHelper.getUserAsync();
82    console.log(`Hello, ${user?.displayName}!`);
83    // For Work/school accounts, email is in mail property
84    // Personal accounts, email is in userPrincipalName
85    console.log(`Email: ${user?.mail ?? user?.userPrincipalName ?? ''}`);
86  } catch (err) {
87    console.log(`Error getting user: ${err}`);
88  }
89}
90// </GreetUserSnippet>
91
92// <DisplayAccessTokenSnippet>
93async function displayAccessTokenAsync() {
94  try {
95    const userToken = await graphHelper.getUserTokenAsync();
96    console.log(`User token: ${userToken}`);
97  } catch (err) {
98    console.log(`Error getting user access token: ${err}`);
99  }
100}
101// </DisplayAccessTokenSnippet>
102
103// <ListInboxSnippet>
104async function listInboxAsync() {
105  try {
106    const messagePage = await graphHelper.getInboxAsync();
107    const messages = messagePage.value;
108
109    // Output each message's details
110    for (const message of messages) {
111      console.log(`Message: ${message.subject ?? 'NO SUBJECT'}`);
112      console.log(`  From: ${message.from?.emailAddress?.name ?? 'UNKNOWN'}`);
113      console.log(`  Status: ${message.isRead ? 'Read' : 'Unread'}`);
114      console.log(`  Received: ${message.receivedDateTime}`);
115    }
116
117    // If @odata.nextLink is not undefined, there are more messages
118    // available on the server
119    const moreAvailable = messagePage['@odata.nextLink'] != undefined;
120    console.log(`\nMore messages available? ${moreAvailable}`);
121  } catch (err) {
122    console.log(`Error getting user's inbox: ${err}`);
123  }
124}
125// </ListInboxSnippet>
126
127// <SendMailSnippet>
128async function sendMailAsync() {
129  try {
130    // Send mail to the signed-in user
131    // Get the user for their email address
132    const user = await graphHelper.getUserAsync();
133    const userEmail = user?.mail ?? user?.userPrincipalName;
134
135    if (!userEmail) {
136      console.log('Couldn\'t get your email address, canceling...');
137      return;
138    }
139
140    await graphHelper.sendMailAsync('Testing Microsoft Graph',
141      'Hello world!', userEmail);
142    console.log('Mail sent.');
143  } catch (err) {
144    console.log(`Error sending mail: ${err}`);
145  }
146}
147// </SendMailSnippet>
148
149// <ListUsersSnippet>
150async function listUsersAsync() {
151  try {
152    const userPage = await graphHelper.getUsersAsync();
153    const users = userPage.value;
154
155    // Output each user's details
156    for (const user of users) {
157      console.log(`User: ${user.displayName ?? 'NO NAME'}`);
158      console.log(`  ID: ${user.id}`);
159      console.log(`  Email: ${user.mail ?? 'NO EMAIL'}`);
160    }
161
162    // If @odata.nextLink is not undefined, there are more users
163    // available on the server
164    const moreAvailable = userPage['@odata.nextLink'] != undefined;
165    console.log(`\nMore users available? ${moreAvailable}`);
166  } catch (err) {
167    console.log(`Error getting users: ${err}`);
168  }
169}
170// </ListUsersSnippet>
171
172// <MakeGraphCallSnippet>
173async function makeGraphCallAsync() {
174  try {
175    await graphHelper.makeGraphCallAsync();
176  } catch (err) {
177    console.log(`Error making Graph call: ${err}`);
178  }
179}
180// </MakeGraphCallSnippet>

graphHelper.js

TEXT
1// Copyright (c) Microsoft Corporation. All rights reserved.
2// Licensed under the MIT license.
3
4// <UserAuthConfigSnippet>
5require('isomorphic-fetch');
6const azure = require('@azure/identity');
7const graph = require('@microsoft/microsoft-graph-client');
8const authProviders =
9  require('@microsoft/microsoft-graph-client/authProviders/azureTokenCredentials');
10
11let _settings = undefined;
12let _deviceCodeCredential = undefined;
13let _userClient = undefined;
14
15function initializeGraphForUserAuth(settings, deviceCodePrompt) {
16  // Ensure settings isn't null
17  if (!settings) {
18    throw new Error('Settings cannot be undefined');
19  }
20
21  _settings = settings;
22
23  _deviceCodeCredential = new azure.DeviceCodeCredential({
24    clientId: settings.clientId,
25    tenantId: settings.authTenant,
26    userPromptCallback: deviceCodePrompt
27  });
28
29  const authProvider = new authProviders.TokenCredentialAuthenticationProvider(
30    _deviceCodeCredential, {
31      scopes: settings.graphUserScopes
32    });
33
34  _userClient = graph.Client.initWithMiddleware({
35    authProvider: authProvider
36  });
37}
38module.exports.initializeGraphForUserAuth = initializeGraphForUserAuth;
39// </UserAuthConfigSnippet>
40
41// <GetUserTokenSnippet>
42async function getUserTokenAsync() {
43  // Ensure credential isn't undefined
44  if (!_deviceCodeCredential) {
45    throw new Error('Graph has not been initialized for user auth');
46  }
47
48  // Ensure scopes isn't undefined
49  if (!_settings?.graphUserScopes) {
50    throw new Error('Setting "scopes" cannot be undefined');
51  }
52
53  // Request token with given scopes
54  const response = await _deviceCodeCredential.getToken(_settings?.graphUserScopes);
55  return response.token;
56}
57module.exports.getUserTokenAsync = getUserTokenAsync;
58// </GetUserTokenSnippet>
59
60// <GetUserSnippet>
61async function getUserAsync() {
62  // Ensure client isn't undefined
63  if (!_userClient) {
64    throw new Error('Graph has not been initialized for user auth');
65  }
66
67  return _userClient.api('/me')
68    // Only request specific properties
69    .select(['displayName', 'mail', 'userPrincipalName'])
70    .get();
71}
72module.exports.getUserAsync = getUserAsync;
73// </GetUserSnippet>
74
75// <GetInboxSnippet>
76async function getInboxAsync() {
77  // Ensure client isn't undefined
78  if (!_userClient) {
79    throw new Error('Graph has not been initialized for user auth');
80  }
81
82  return _userClient.api('/me/mailFolders/inbox/messages')
83    .select(['from', 'isRead', 'receivedDateTime', 'subject'])
84    .top(25)
85    .orderby('receivedDateTime DESC')
86    .get();
87}
88module.exports.getInboxAsync = getInboxAsync;
89// </GetInboxSnippet>
90
91// <SendMailSnippet>
92async function sendMailAsync(subject, body, recipient) {
93  // Ensure client isn't undefined
94  if (!_userClient) {
95    throw new Error('Graph has not been initialized for user auth');
96  }
97
98  // Create a new message
99  const message = {
100    subject: subject,
101    body: {
102      content: body,
103      contentType: 'text'
104    },
105    toRecipients: [
106      {
107        emailAddress: {
108          address: recipient
109        }
110      }
111    ]
112  };
113
114  // Send the message
115  return _userClient.api('me/sendMail')
116    .post({
117      message: message
118    });
119}
120module.exports.sendMailAsync = sendMailAsync;
121// </SendMailSnippet>
122
123// <AppOnyAuthConfigSnippet>
124let _clientSecretCredential = undefined;
125let _appClient = undefined;
126
127function ensureGraphForAppOnlyAuth() {
128  // Ensure settings isn't null
129  if (!_settings) {
130    throw new Error('Settings cannot be undefined');
131  }
132
133  if (!_clientSecretCredential) {
134    _clientSecretCredential = new azure.ClientSecretCredential(
135      _settings.tenantId,
136      _settings.clientId,
137      _settings.clientSecret
138    );
139  }
140
141  if (!_appClient) {
142    const authProvider = new authProviders.TokenCredentialAuthenticationProvider(
143      _clientSecretCredential, {
144        scopes: [ 'https://graph.microsoft.com/.default' ]
145      });
146
147    _appClient = graph.Client.initWithMiddleware({
148      authProvider: authProvider
149    });
150  }
151}
152// </AppOnyAuthConfigSnippet>
153
154// <GetUsersSnippet>
155async function getUsersAsync() {
156  ensureGraphForAppOnlyAuth();
157
158  return _appClient?.api('/users')
159    .select(['displayName', 'id', 'mail'])
160    .top(25)
161    .orderby('displayName')
162    .get();
163}
164module.exports.getUsersAsync = getUsersAsync;
165// </GetUsersSnippet>
166
167// <MakeGraphCallSnippet>
168// This function serves as a playground for testing Graph snippets
169// or other code
170async function makeGraphCallAsync() {
171  // INSERT YOUR CODE HERE
172  // Note: if using _appClient, be sure to call ensureGraphForAppOnlyAuth
173  // before using it.
174  // ensureGraphForAppOnlyAuth();
175}
176module.exports.makeGraphCallAsync = makeGraphCallAsync;
177// </MakeGraphCallSnippet>

appSettings.js

TEXT
1// Copyright (c) Microsoft Corporation. All rights reserved.
2// Licensed under the MIT license.
3
4const settings = {
5  'clientId': 'YOUR_CLIENT_ID_HERE',
6  'clientSecret': 'YOUR_CLIENT_SECRET_HERE_IF_USING_APP_ONLY',
7  'tenantId': 'YOUR_TENANT_ID_HERE_IF_USING_APP_ONLY',
8  'authTenant': 'common',
9  'graphUserScopes': [
10    'user.read',
11    'mail.read',
12    'mail.send'
13  ]
14};
15
16module.exports = settings;