WPF Azure AD signin with Sharepoint Online API call using Graph API

This article shows how a native WPF application could authenticate and authorize using an Azure Active Directory App Registration and then upload and download files in Sharepoint Online. The Graph API is used to access Sharepoint. Other Sharepoint libraries will NOT work if using an Azure AD signin.

Code: https://github.com/damienbod/WpfAzureADSharepointOnlineGraphApi

Setup the Azure Active Directory App Registration

An Azure AD App Registration needs to be created in the same Azure AD as the Sharepoint Online. This is used to configure the signin, and also the Graph API permissions. One way is to open the Microsoft admin UI and login using the following link:

https://admin.microsoft.com

Click the ‘Show All’ and then the ‘Azure Active Directory’ menus. This will open the aad UI:

https://aad.portal.azure.com

You should now be in the correct subscription, Active Directory. You could open this directly if you know which Azure AD, the Sharepoint Online belongs to. If you only belong to one subscription, this is not a problem.

Now configure a new App Registration.

Create a new public native client and add the redirect URL:

urn:ietf:wg:oauth:2.0:oob

This is the URL which is configured by the MSAL native client.

Add the Sharepoint API permissions. Click the ‘View API Permissions’ button, and then Sharepoint. You want to add delegated permissions. Add the permissions as required. We use the ‘AllSites.FullControl’ scope in this demo.

Implement the login using MSAL

Install the Microsoft Authentication Library for .NET (MSAL.NET). This library is used to login to Azure AD and request the scopes required to use Sharepoint Online via the Graph API.

The code was implemented using the Azure examples from github. Configure the ClientId and the Tenant in the App.xaml.cs

private static string ClientId = "your_client_id_from_app_registrition"; 
private static string Tenant = "your_tenant_id_from_azure_ad";

Add the Sharepoint scope and login. This code was taken from the Azure AD examples. When the user logs in, a web browser will be opened, and the Azure AD login can be completed, using MFA if required, and the browser window will be closed, which is really neat.

string[] scopes = new string[] {   "user.read", "AllSites.FullControl" };

private async void CallGraphButton_Click(object sender, RoutedEventArgs e)
{
	AuthenticationResult authResult = null;
	var app = App.PublicClientApp;
	ResultText.Text = string.Empty;
	//TokenInfoText.Text = string.Empty;

	var accounts = await app.GetAccountsAsync();
	var firstAccount = accounts.FirstOrDefault();

	try
	{
		var tt = app.Authority;
		authResult = await app.AcquireTokenSilent(scopes, firstAccount)
			.ExecuteAsync();
	}
	catch (MsalUiRequiredException ex)
	{
		// A MsalUiRequiredException happened on AcquireTokenSilent. 
		// This indicates you need to call AcquireTokenInteractive to acquire a token
		System.Diagnostics.Debug.WriteLine($"MsalUiRequiredException: {ex.Message}");

		try
		{
			authResult = await app.AcquireTokenInteractive(scopes)
				.WithAccount(accounts.FirstOrDefault())
				.WithParentActivityOrWindow(new WindowInteropHelper(this).Handle) // optional, used to center the browser on the window
				.WithPrompt(Microsoft.Identity.Client.Prompt.SelectAccount)
				.ExecuteAsync();
		}
		catch (MsalException msalex)
		{
			ResultText.Text = $"Error Acquiring Token:{System.Environment.NewLine}{msalex}";
		}
	}
	catch (Exception ex)
	{
		ResultText.Text = $"Error Acquiring Token Silently:{System.Environment.NewLine}{ex}";
		return;
	}

	if (authResult != null)
	{
		await DoGraphApiCalls(authResult);

		DisplayBasicTokenInfo(authResult);
		this.SignOutButton.Visibility = Visibility.Visible;
	}
}

Setup Sharepoint Online

We want the WPF application to upload, download files to Sharepoint Online. This needs to be configured in Sharepoint, and the Graph API calls need to match this.

The page https://damienbodsharepoint.sharepoint.com/sites/listview was created, and a folder TestDocs was added.

Implement the Graph API calls to Sharepoint Online

Download and install the Microsoft Graph C# nuget package. Microsoft Graph Client Library makes it easy to use the API, but you could also just make plain HTTP requests.

Set the Sharepoint domain, relative path for the files, and the folder to use which matches the Sharepoint Online page you want to work with.

var sharepointDomain = "damienbodsharepoint.sharepoint.com";
var relativePath = "/sites/ListView";
var folderToUse = "TestDocs";

Add the access token from the MSAL signin to the GraphServiceClient client.

GraphServiceClient graphClient = new GraphServiceClient(
 new DelegateAuthenticationProvider(
   async (requestMessage) => {
	requestMessage.Headers.Authorization =
	 new AuthenticationHeaderValue("bearer",
		authResult.AccessToken);
  }));

And upload or view the files as required.

private async Task DoGraphApiCalls(AuthenticationResult authResult)
{
	var sharepointDomain = "damienbodsharepoint.sharepoint.com";
	var relativePath = "/sites/ListView";
	var folderToUse = "TestDocs";

	GraphServiceClient graphClient = new GraphServiceClient(
		new DelegateAuthenticationProvider(
			async (requestMessage) =>
			{
				requestMessage.Headers.Authorization =
					new AuthenticationHeaderValue("bearer",
						authResult.AccessToken);
			}));

	var site = await graphClient
		.Sites[sharepointDomain]
		.SiteWithPath(relativePath)
		.Request()
		.GetAsync();

	var drive = await graphClient
		.Sites[site.Id]
		.Drive
		.Request()
		.GetAsync();

	var items = await graphClient
		.Sites[site.Id]
		.Drives[drive.Id]
		.Root
		.Children
		.Request().GetAsync();

	// folder to upload to
	var folder = items
		.FirstOrDefault(f => f.Folder != null && f.WebUrl.Contains(folderToUse));

	// Upload file
	string path = @"dummy.txt";
	byte[] data = System.IO.File.ReadAllBytes(path);
	Stream stream = new MemoryStream(data);
	await graphClient.Sites[site.Id]
			.Drives[drive.Id]
			.Items[folder.Id]
			.ItemWithPath("dummy1.txt")
			.Content
			.Request()
			.PutAsync<DriveItem>(stream);


	string fileNames = string.Empty;
	var files = await graphClient
		.Sites[site.Id]
		.Drives[drive.Id]
		.Items[folder.Id]
		.Children
		.Request().GetAsync();

	foreach (var file in files)
	{
		fileNames = $"{fileNames} {file.Name}";
	}

	ResultText.Text = fileNames;
}

This works well, and can be setup fairly easy, once you understand the moving parts. The C# Graph API is a little bit hard to understand at first, and the docs are not really good for this. Other Sharepoint .NET nuget packages will not work with the MSAL and the Azure AD signin. These were made to be used directly with Sharepoint.

Links:

https://docs.microsoft.com/en-us/graph/api/resources/sharepoint?view=graph-rest-1.0

https://admin.microsoft.com/Adminportal/Home#/homepage

https://aad.portal.azure.com/

https://www.c-sharpcorner.com/article/microsoft-graph-api-access-documents-from-sharepoint-document-library-using-az/

Office 365 Graph API: download document from SharePoint library

https://docs.microsoft.com/en-us/graph/api/resources/sharepoint?view=graph-rest-1.0

https://www.jonathanhuss.com/intro-to-the-microsoft-graph-net-client-library/

One comment

  1. […] WPF Azure AD signin with Sharepoint Online API call using Graph API (Damien Bowden) […]

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.