JavaScript and Microsoft Graph
Neil Haddley • May 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 selected the "App registrations" link

I selected the "New registration" link

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 created a minimal node project

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 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

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;