Skip to content

Instantly share code, notes, and snippets.

@kalyankrishna1
Last active January 10, 2022 17:20
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kalyankrishna1/0a6ea6d00a09f7ae81c89960e1038d79 to your computer and use it in GitHub Desktop.
Save kalyankrishna1/0a6ea6d00a09f7ae81c89960e1038d79 to your computer and use it in GitHub Desktop.
Code and steps for the session "An introduction to Microsoft Graph for developers - Getting Started"
/**
1. Create a.NET Core console client app.
2. Add a new Json file named "appsettings.json" in your project, set it property "Copy to output directory" to "Copy if newer".
3. Add the following lines in the appsettings.json
{
"Instance": "https://login.microsoftonline.com/",
"ClientId": "The clientid/appid that you got from the app registration",
"TenantId": "Your Azure AD tenant's id (a guid)",
"RedirectUri": "http://localhost",
"TenantDomain": "Your tenant's domain, like <mytenant>.onmicrosoft.com",
"ClientSecret": "The client secret that you generated in your the app registration",
"GraphApiEndpoint": "https://graph.microsoft.com/V1.0/"
}
4.Register your app on the Azure AD application registration portal
4.1 In the "Authentication" blade, choose "Add a platform" -> "Mobile and desktop applications" and add "http://localhost" as the redirect Uri.
4.2 Update the appsettings.json from various attributes of the registered app.
5. Add the following Nuget packages. Checkbox in Nuget package manager window.
Microsoft.Graph
Microsoft.Identity.Client
Newtonsoft.Json
Microsoft.Extensions.Configuration.Json
Microsoft.Extensions.Configuration
Microsoft.Extensions.Configuration.FileExtensions
Microsoft.Extensions.Configuration.Binder
**/
using Microsoft.Extensions.Configuration;
using Microsoft.Graph;
using Microsoft.Identity.Client;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http.Headers;
using System.Threading.Tasks;
namespace MSGraphSessionDemos
{
/// <summary>
/// This console program showcases how to use MSAL library to get an Access token for Microsoft Graph and call the various APIs using the Microsoft Graph SDK.
/// </summary>
internal class Program
{
private static IConfiguration _configuration;
private static string _tenantDomain;
//private static string[] _graphScopes = new string[] { $"https://graph.microsoft.com/.default" };
private static string[] _graphScopes = new string[] { $"user.read", "user.readwrite.all" };
/// <summary>
/// The access token for MS Graph
/// </summary>
/// NOTE: Do not use in prod apps as-is. you should be using a cached token obtained via MSAL. See aka.ms/aadcodesamples for a sample matching your app type and authN scenario
private static string _GraphAccessToken = string.Empty;
// Change the following between each call to create/update user if not deleting the user
private static string givenName = "test99";
private static string surname = "user99";
private static async Task Main(string[] args)
{
LoadConfig();
// Prepare an autheticated MS Graph SDK client
GraphServiceClient graphServiceClient = GetAuthenticatedGraphClient();
// Call Graph APIs
// Get information from Graph about the currently signed-In user
Console.WriteLine("--Fetching details of the currently signed-in user--");
await GetMeAsync(graphServiceClient);
Console.WriteLine("---------");
// Create a new user
Console.WriteLine($"--Creating a new user in the tenant --");
User newUser = await CreateUserAsync(graphServiceClient);
Console.WriteLine($"--Printing details of the newly created user--");
PrintUserDetails(newUser);
Console.WriteLine("---------");
// Update an existing user
if (newUser != null)
{
Console.WriteLine("--Updating the detail of an existing user--");
User updatedUser = await UpdateUserAsync(graphServiceClient, userId: newUser.Id, jobTitle: "Program Manager");
Console.WriteLine($"--Printing details of the updated user--");
PrintUserDetails(updatedUser);
Console.WriteLine("---------");
}
// List existing users
Console.WriteLine("--Listing all users in the tenant--");
List<User> users = await GetUsersAsync(graphServiceClient);
users.ForEach(u => PrintUserDetails(u));
Console.WriteLine("---------");
// Delete this user
Console.WriteLine("--Deleting a user in the tenant--");
if (newUser != null)
{
await DeleteUserAsync(graphServiceClient, newUser?.Id);
}
Console.WriteLine("---------");
// List existing users after deletion
Console.WriteLine("--Listing all users in the tenant after deleting a user.--");
users = await GetUsersAsync(graphServiceClient);
users.ForEach(u => PrintUserDetails(u));
Console.WriteLine("---------");
Console.WriteLine("Press any key to exit");
Console.ReadKey();
}
/// <summary>Calls the /me and /me/directreports endpoints of Microsoft Graph using the MS Graph SDK.</summary>
/// <param name="graphServiceClient">The graph service client.</param>
private static async Task GetMeAsync(GraphServiceClient graphServiceClient)
{
// Call /me Api
Console.WriteLine($"GET {graphServiceClient.Me.Request().RequestUrl}");
var me = await graphServiceClient.Me.Request().GetAsync();
Console.WriteLine($"Display Name from /me->{me.DisplayName}");
// /me/directReports
Console.WriteLine($"GET {graphServiceClient.Me.DirectReports.Request().RequestUrl}");
var directreports = await graphServiceClient.Me.DirectReports.Request().GetAsync();
foreach (User user in directreports.CurrentPage)
{
Console.WriteLine($"Direct report's Display Name ->{user.DisplayName}");
}
}
/// <summary>
/// Creates a user record in the Azure AD tenant using Microsoft Graph and the MS Graph SDK.
/// </summary>
/// <param name="graphServiceClient">The graph service client.</param>
/// <returns></returns>
private static async Task<User> CreateUserAsync(GraphServiceClient graphServiceClient)
{
User newUserObject = null;
string displayname = $"{givenName} {surname}";
string mailNickName = $"{givenName}{surname}";
string upn = $"{mailNickName}@{_tenantDomain}";
string password = "p@$$w0rd!";
try
{
var newuser = new User
{
AccountEnabled = true,
DisplayName = displayname,
MailNickname = mailNickName,
GivenName = givenName,
Surname = surname,
PasswordProfile = new PasswordProfile
{
Password = password
},
UserPrincipalName = upn
};
Console.WriteLine($"POST {graphServiceClient.Users.Request().RequestUrl}");
newUserObject = await graphServiceClient.Users.Request().AddAsync(newuser);
}
catch (ServiceException e)
{
Console.WriteLine("We could not add a new user: " + e.Error.Message);
return null;
}
return newUserObject;
}
/// <summary>Updates a user's record in an Azure AD tenant using Microsoft Graph and the MS Graph SDK.</summary>
/// <param name="graphServiceClient">The graph service client.</param>
/// <param name="userId">The user identifier.</param>
/// <param name="jobTitle">The job title.</param>
/// <returns>
/// <br />
/// </returns>
private static async Task<User> UpdateUserAsync(GraphServiceClient graphServiceClient, string userId, string jobTitle)
{
try
{
// Update the user.
Console.WriteLine($"PATCH {graphServiceClient.Users.Request().RequestUrl}");
await graphServiceClient.Users[userId].Request().UpdateAsync(new User
{
JobTitle = jobTitle
});
}
catch (ServiceException e)
{
Console.WriteLine($"We could not update details of the user with Id {userId}: " + $"{e}");
}
return await GetUserByIdAsync(graphServiceClient, userId);
}
/// <summary>Gets a user's record suing their objectId in an Azure AD tenant using Microsoft Graph and the MS Graph SDK.</summary>
/// <param name="graphServiceClient">The graph service client.</param>
/// <param name="principalId">The principal identifier.</param>
/// <returns>
/// <br />
/// </returns>
private static async Task<User> GetUserByIdAsync(GraphServiceClient graphServiceClient, string principalId)
{
Console.WriteLine($"GET {graphServiceClient.Users.Request().Filter($"id eq '{principalId}'").RequestUrl}");
var users = await graphServiceClient.Users.Request().Filter($"id eq '{principalId}'").GetAsync();
return users.CurrentPage.FirstOrDefault();
}
/// <summary>Gets all the users an Azure AD tenant from Microsoft Graph API and the MS Graph SDK.</summary>
/// <param name="graphServiceClient">The graph service client.</param>
/// <returns>
/// <br />
/// </returns>
private static async Task<List<User>> GetUsersAsync(GraphServiceClient graphServiceClient)
{
List<User> allUsers = new List<User>();
try
{
Console.WriteLine($"GET {graphServiceClient.Users.Request().Top(150).RequestUrl}");
IGraphServiceUsersCollectionPage users = await graphServiceClient.Users.Request().Top(150).GetAsync();
allUsers = await ProcessIGraphServiceUsersCollectionPage(graphServiceClient, users);
}
catch (ServiceException e)
{
Console.WriteLine("We could not retrieve the user's list: " + $"{e}");
return null;
}
return allUsers;
}
/// <summary>Deletes a user in an Azure AD tenant via the Microsoft Graph API and the MS Graph SDK.</summary>
/// <param name="graphServiceClient">The graph service client.</param>
/// <param name="userId">The user identifier.</param>
private static async Task DeleteUserAsync(GraphServiceClient graphServiceClient, string userId)
{
try
{
Console.WriteLine($"DELETE {graphServiceClient.Users[userId].Request().RequestUrl}");
await graphServiceClient.Users[userId].Request().DeleteAsync();
Console.WriteLine($"Successfully deleted user with Id-{userId}");
}
catch (ServiceException e)
{
Console.WriteLine($"We could not delete the user with Id-{userId}: " + $"{e}");
}
}
#region Helper Methods
/// <summary>
/// Processes the IGraphServiceUsersCollectionPage page.
/// </summary>
/// <param name="graphServiceClient">The graph service client.</param>
/// <param name="usersPage">The users collection page.</param>
/// <returns></returns>
private static async Task<List<User>> ProcessIGraphServiceUsersCollectionPage(GraphServiceClient graphServiceClient, IGraphServiceUsersCollectionPage usersPage)
{
return await CollectionProcessor<User>.ProcessGraphCollectionPageAsync(graphServiceClient, usersPage);
}
/// <summary>Prints a given user's details.</summary>
/// <param name="user">The user.</param>
private static void PrintUserDetails(User user)
{
if (user != null)
{
Console.WriteLine($"DisplayName-{user.DisplayName}, MailNickname- {user.MailNickname}, GivenName-{user.GivenName}, Surname-{user.Surname}, Upn-{user.UserPrincipalName}, JobTitle-{user.JobTitle}, Id-{user.Id}");
}
else
{
Console.WriteLine("The provided User is null!");
}
}
private static void LoadConfig()
{
// Using appsettings.json as our configuration settings
var builder = new ConfigurationBuilder()
.SetBasePath(System.IO.Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json");
_configuration = builder.Build();
_tenantDomain = _configuration.GetValue<string>("TenantDomain");
}
#endregion Helper Methods
#region Prepare an authenticated MS Graph SDK client
/// <summary>
/// An example of how to authenticate the Microsoft Graph SDK using the MSAL library
/// </summary>
/// <returns></returns>
private static GraphServiceClient GetAuthenticatedGraphClient()
{
string GraphApiEndpoint = _configuration.GetValue<string>("GraphApiEndpoint");
IPublicClientApplication app = BuildPublicClientApp();
GraphServiceClient graphServiceClient =
new GraphServiceClient(GraphApiEndpoint, new DelegateAuthenticationProvider(async (requestMessage) =>
{
// Retrieve an access token for Microsoft Graph (gets a fresh token if needed).
await AuthenticateUsingMsalAsync(app);
// Add the access token in the Authorization header of the API request.
requestMessage.Headers.Authorization =
new AuthenticationHeaderValue("Bearer", _GraphAccessToken);
}));
return graphServiceClient;
}
/// <summary>
/// Initializes the MSAL's IPublicClientApplication from the configuration
/// </summary>
/// <returns></returns>
private static IPublicClientApplication BuildPublicClientApp()
{
PublicClientApplicationOptions appConfiguration = _configuration.Get<PublicClientApplicationOptions>();
string authority = string.Concat(appConfiguration.Instance, appConfiguration.TenantId);
// Building a public client application
IPublicClientApplication app = PublicClientApplicationBuilder.Create(appConfiguration.ClientId)
.WithAuthority(authority)
.WithRedirectUri(appConfiguration.RedirectUri)
//.WithExtraQueryParameters("login_hint=kkrishna@devworkshopdemo.onmicrosoft.com")
.Build();
return app;
}
/// <summary>
/// Acquiring token for Graph using the MSAL SDK
/// </summary>
/// <param name="app">The IPublicClientApplication instance.</param>
/// <returns></returns>
/// <exception cref="InvalidOperationException">Failed to obtain the JWT token for Graph</exception>
private static async Task<string> AuthenticateUsingMsalAsync(IPublicClientApplication app)
{
// Warning: This does not check for expired token. In production apps, you will always use token cache instead of using variables like here.
if (!string.IsNullOrWhiteSpace(_GraphAccessToken))
{
return _GraphAccessToken;
}
var GraphResult = await app.AcquireTokenInteractive(_graphScopes).ExecuteAsync();
if (GraphResult == null)
{
throw new InvalidOperationException("Failed to obtain the JWT token for Graph");
}
_GraphAccessToken = GraphResult.AccessToken;
return _GraphAccessToken;
}
#endregion Prepare an authenticated MS Graph SDK client
}
/// <summary>
/// Helper class to handle pagination with MS Graph SDK
/// </summary>
/// <typeparam name="T"></typeparam>
public static class CollectionProcessor<T>
{
/// <summary>
/// Processes the Ms Graph collection page.
/// </summary>
/// <param name="graphServiceClient">The graph service client.</param>
/// <param name="collectionPage">The collection page.</param>
/// <returns></returns>
public static async Task<List<T>> ProcessGraphCollectionPageAsync(GraphServiceClient graphServiceClient, ICollectionPage<T> collectionPage)
{
List<T> allItems = new List<T>();
var pageIterator = PageIterator<T>.CreatePageIterator(graphServiceClient, collectionPage, (item) =>
{
//Console.WriteLine(user);
allItems.Add(item);
return true;
});
// Start iteration
await pageIterator.IterateAsync();
while (pageIterator.State != PagingState.Complete)
{
// Keep iterating till complete.
await pageIterator.ResumeAsync();
}
return allItems;
}
}
}
@penguintechlinux
Copy link

penguintechlinux commented Jan 29, 2020

Hi,
I have registered an app testapp in Azure and filled the below :

const string clientId = "Enter clientId from Portal";
const string tenant = ".onmicrosoft.com";
const string redirectUri = "<redirect uri in portal starting with 'msal'>";

when i put tenant = XXXXXXXX.onmicrosoft.com
Program was showing error and then also redirect uri starting with msal showing exception .

MsalClientException: Only loopback redirect uri is supported, but msal9XXXXXXXXXX//auth/ was found. Configure http://localhost or http://localhost:port both during app registration and when you create the PublicClientApplication object. See https://aka.ms/msal-net-os-browser for details

So ,i changed my code to const string redirectUri = "http://localhost";
Then i changed it to : const string tenant =""

So,now after running program its redirecting to
Need admin approval
TestPointApi
TestPointApi needs permission to access resources in your organization that only an admin can grant. Please ask an admin to grant permission to this app before you can use it.

So,please guide me what should be next step.

@penguintechlinux
Copy link

penguintechlinux commented Jan 29, 2020

After resolving above using : string[] scopes = new string[] { "user.read" };
Now, i am getting Exception : NullReferenceException: Object reference not set to an instance of an object.

At GetMeAsync(graphServiceClient).GetAwaiter().GetResult(); at line 51.

// Get information from Graph about the currently signed-In user GetMeAsync(graphServiceClient).GetAwaiter().GetResult(); This is the line , but i can see that graphServiceClient params are populated with values like BatchUrl "graph.microsoft.com/v1.0" yes,getMeAsync doesn't return anything ,just before this line app is redirected to logging in to app, and authentication is successful but finally exception thrown
Please help.
ADGraphSvc1
ADGraphSvc2

@kalyankrishna1
Copy link
Author

It looks like you are using .NET core. See the first comment in the gist !

To troubleshoot SDK issues

  1. Use a tool like fiddler to intercept the actual REST Api calls
  2. Use Graph Explorer (https://aka.ms/ge) to see if the Api has any issues.

@kalyankrishna1
Copy link
Author

kalyankrishna1 commented May 8, 2020

If using the .NET Core console application, us the following link

https://gist.github.com/kalyankrishna1/d3ba31bf30ee3dfe2c757d4d29d2b030#file-netcoreprogram-cs

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment