draconastar
draconastar

Reputation: 455

SharePointOnline CSOM 401 Unauthorized Using Provided Access Token

I am building a feature that automates the retrieval of documents and other SharePoint files from a Web API, but I'm having a difficult time getting authorized to perform even basic read operations. I am testing this in a .NET 6 console application using the Microsoft.SharePointOnline.CSOM NuGet package.

I have registered an application in Azure Active Directory and given it the Sites.Read.All permission. I've taken the ClientID, ClientSecret and TenantID as reported by that registered application and I'm using those in my console application. I can retrieve an access token without issue, and decoding that JWT shows that it comes with Sites.Read.All permission. But regardless of what I try, ClientContext.ExecuteQueryAsync() consistently throws an exception complaining that the remote server responded with a 401.

Here is the code that I'm testing this with:

var clientId = "myClientId";
var clientSecret = "myClientSecret";
var tenantId = "myTenantId";
var authority = "https://login.microsoftonline.com/" + tenantId;
var siteUrl = "https://myorg.sharepoint.com";

var app = new ConfidentialClientApplicationBuilder
    .Create()
    .WithClientSecret(clientSecret)
    .WithAuthority(authority)
    .WithTenantId(tenantId)
    .Build();

var paramBuilder = app.AcquireTokenForClient(new[] { siteUrl + "/.default" });
var authResult = await paramBuilder.ExecuteAsync();

// authResult has successfully retrieved an access token at this point

var context = new ClientContext(siteUrl);
context.ExecutingWebRequest += (_, e) =>
{
    e.WebRequestExecutor.RequestHeaders["Authorization"] = "Bearer " + authResult.AccessToken;
}

context.Load(context.Web);

await context.ExecuteQueryAsync(); // 401 is thrown here

var title = context.Web.Title;

I have tried several different ways of getting around this to no avail:

"The resource principal named https://myorg.sharepoint.com/sites/mysite was not found in the tenant named (my tenant). This can happen if the application has not been installed by the administrator of the tenant or consented to by any user in the tenant. You might have sent your authentication request to the wrong tenant.

My application seems to have the permissions it needs to do this basic read operation, but I am continually rebuffed. I am using the ClientID, ClientSecret and Tenant ID as copied directly from the AAD application page. The code I'm using above is recommended by Microsoft to use the new SharePointOnline.CSOM package. What am I missing here?

Upvotes: 0

Views: 2231

Answers (3)

Sanju Chilukuri
Sanju Chilukuri

Reputation: 11

I also got same issue. For me user access token worked. rather than app access token.

public async Task<string> GetAccessToken()
    {
        if (_authToken == null)
        {
            var tenantId = Configuration.TenantId;
            var clientId = Configuration.ClientId;

            var authority = $"https://login.microsoftonline.com/{tenantId}";

            var scopes = new string[] { "https://domain.sharepoint.com/.default" };

            var cca = PublicClientApplicationBuilder.Create(clientId)
                .WithRedirectUri("http://localhost")
                .WithAuthority(new Uri($"https://login.microsoftonline.com/{tenantId}"))
                .Build();

            var result = await cca.AcquireTokenInteractive(scopes).ExecuteAsync();
            return _authToken = result.AccessToken;
        }
        return _authToken;

    }

But don't forget to add redirect URL in Azure. under Home>>App registartions>>App>>Authentication.

Azure Redirect URI

Upvotes: 0

draconastar
draconastar

Reputation: 455

I wound up "solving" this problem by using the PnP.Framework NuGet package instead of Microsoft.SharePointOnline.CSOM. I changed nothing else about my app registration or its designated permissions, and PnP.Framework was able to handle it without issue (and with fewer arguments). It seems to know something that SharePointOnline.CSOM doesn't considering that the following simple console app works:

using System;
using PnP.Framework

const string clientId = "myClientId";
const string clientSecret = "myClientSecret";
const string siteUrl = "https://myorg.sharepoint.com/sites/mysite";

using var clientContext = new AuthenticationManager()
                             .GetACSAppOnlyContext(siteUrl, clientId, clientSecret);

cc.Load(cc.Web);

await cc.ExecuteQueryAsync(); // no longer throws a 401

Console.WriteLine(cc.Web.Title); // prints my site's title

I tried to use the newer PnP.Core SDK, but I couldn't find any documentation or examples on how to get that package working with an app-only client secret authenticated context. PnP.Framework's API is the cleanest and most reliable that I've found as of yet.

Upvotes: 1

user2250152
user2250152

Reputation: 20768

Constructor of ClientContext requires site url including site name.

var clientId = "myClientId";
var clientSecret = "myClientSecret";
var tenantId = "myTenantId";
var authority = "https://login.microsoftonline.com/" + tenantId;
var siteUrl = "https://myorg.sharepoint.com";
var siteName = "MySiteName";

var app = new ConfidentialClientApplicationBuilder
    .Create()
    .WithClientSecret(clientSecret)
    .WithAuthority(authority)
    .WithTenantId(tenantId)
    .Build();

var paramBuilder = app.AcquireTokenForClient(new[] { siteUrl + "/.default" });
var authResult = await paramBuilder.ExecuteAsync();

// authResult has successfully retrieved an access token at this point

var webFullUrl = $"{siteUrl}/sites/{siteName}";
var context = new ClientContext(webFullUrl);

If the site has some prefix

var webFullUrl = $"{siteUrl}/sites/{sitePrefix}/{siteName}";

Upvotes: 0

Related Questions