robiwahn
robiwahn

Reputation: 33

Access Sharepoint Online Files from .NET Worker Service via Microsoft Graph/OAuth - Unauthorized or Access Denied error (401)

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:

  1. Register an application (Single tenant)
  2. API permissions -> Add a permission -> SharePoint -> Application permissions -> Sites.FullControl.All
  3. Grant admin consent confirmation.
  4. Certificates & secrets -> Add a client secret

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.

Upvotes: 3

Views: 207

Answers (2)

Joe
Joe

Reputation: 1669

    public static void InitializeAuthentication(string clientId, string tenantId, string clientSecret)
    {
        var credential = new ClientSecretCredential(tenantId, clientId, clientSecret);
        _graphClient = new GraphServiceClient(credential);
    }

I was able to use client id, tenet id, and secret value to connect. No cert needed. NOTE: you MUST use the secret VALUE and NOT the Secret's ID.

Upvotes: 0

Rohit Saigal
Rohit Saigal

Reputation: 9674

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:

enter image description here

  • Select Certificates & secrets > Certificates.
  • Click on Upload certificate and select the certificate file to upload.
  • Once the certificate is uploaded, the thumbprint, start date, and expiration values are displayed.

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();
}

Upvotes: 0

Related Questions