I want to create a .NET Worker Service (c#) to access files hosted on Sharepoint online.
I have registered an application for this in the Azure portal as follows:
The code looks like this:
using Microsoft.Identity.Client;
using Microsoft.SharePoint.Client;
string siteUrl = "https://myname.sharepoint.com/sites/sharepointwebsite/";
string clientId = "XXX";
string tenantId = "XXX";
string clientSecret = "XXX";
var confidentialClientApp = ConfidentialClientApplicationBuilder.Create(clientId)
.WithTenantId(tenantId)
.WithClientSecret(clientSecret)
.Build();
var authResult = await confidentialClientApp.AcquireTokenForClient(new[] { "https://myname.sharepoint.com/.default" }).ExecuteAsync();
using (var context = new ClientContext(siteUrl))
{
context.ExecutingWebRequest += (sender, e) =>
{
e.WebRequestExecutor.RequestHeaders["Authorization"] = "Bearer " + authResult.AccessToken;
};
Web web = context.Web;
context.Load(web);
context.ExecuteQuery();
Console.WriteLine("Title: " + web.Title);
}
I get an access token, but access to the SharePoint site is denied (401).
Which step (permissions) may I have forgotten?
Thank you very much for your help.
When you use an Azure AD registered application to authenticate with SharePoint online, you'll need to use a certificate to prove the application’s identity while requesting an access token. In a normal scenario you could use either a secret or a certificate, but AFAIK SharePoint online blocks anything other than certificate. Microsoft documentation for the whole scenario is available here -
Step 1 - Associate certificate credential with your registered application
a. If you have a certificate already you don't need to do this part and simply upload the certificate, otherwise you can create a self-signed certificate using the following PowerShell script. (NOTE: self-signed cert may not be good for production scenarios, but can help you verify the concept in dev)
$certname = "MyTestCertificate"
$cert = New-SelfSignedCertificate -Subject "CN=$certname" -CertStoreLocation "Cert:\CurrentUser\My" -KeyExportPolicy Exportable -KeySpec Signature -KeyLength 2048 -KeyAlgorithm RSA -HashAlgorithm SHA256
Export-Certificate -Cert $cert -FilePath "C:\WorkFolder\$certname.cer"
$mypwd = ConvertTo-SecureString -String "MyStrongPassword123#" -Force -AsPlainText
Export-PfxCertificate -Cert $cert -FilePath "C:\WorkFolder\$certname.pfx" -Password $mypwd
b In the App registrations tab for the client application:
Step 2 - Use certificate instead of client secret to acquire token
I've just taken your C# code and modified it to use a certificate.
Important part:
var confidentialClientApp = ConfidentialClientApplicationBuilder.Create(clientId)
.WithTenantId(tenantId)
.WithCertificate(new X509Certificate2(@"C:\WorkFolder\MyTestCertificate.pfx", "MyStrongPassword123#")).Build();
var authResult = await confidentialClientApp.AcquireTokenForClient(new[] { "https://myname.sharepoint.com/.default" }).ExecuteAsync();
Complete code:
using Microsoft.Identity.Client;
using Microsoft.SharePoint.Client;
using System.Security.Cryptography.X509Certificates;
string siteUrl = "https://myname.sharepoint.com/sites/sharepointwebsite/";
string clientId = "XXX";
string tenantId = "XXX";
var confidentialClientApp = ConfidentialClientApplicationBuilder.Create(clientId)
.WithTenantId(tenantId)
.WithCertificate(new X509Certificate2(@"C:\WorkFolder\MyTestCertificate.pfx", "MyStrongPassword123#")).Build();
var authResult = await confidentialClientApp.AcquireTokenForClient(new[] { "https://myname.sharepoint.com/.default" }).ExecuteAsync();
using (var context = new ClientContext(siteUrl))
{
context.ExecutingWebRequest += (sender, e) =>
{
e.WebRequestExecutor.RequestHeaders["Authorization"] = "Bearer " + authResult.AccessToken;
};
Web web = context.Web;
context.Load(web);
context.ExecuteQuery();
Console.WriteLine("Title: " + web.Title);
Console.ReadLine();
}