One of the key differences compared to the .NET Framework CSOM was that the
authentication is completely independent of CSOM library now. Previously,
there were native classes like
SharePointOnlineCredentials which were used for
auth, but they have been removed now.
Since .NET Standard CSOM now uses OAuth for
authentication, it's up to the developer to get an access token and pass it
along with the call to SharePoint Online. The CSOM library does not care how
the access token was fetched.
So in this post,
let's have a look at getting an Application authentication (aka App-Only)
access token using MSAL.NET and use it with the new .NET Standard CSOM to
get data from SharePoint Online.
When making app-only calls to SharePoint Online, we can either use an Azure AD
app registration (with the Client Certificate) or we can use SharePoint
App-Only authentication created via the AppRegNew.aspx and AppInv.aspx pages.
(There are other workarounds available but that would be out of scope for this
post) I go into more details about this in my previous post: Working with Application Permissions (App-Only Auth) in SharePoint Online
and the Microsoft Graph
The recommended approach is to go with an Azure AD App Registration and the
Client Certificate approach so that is what we will be using. To do that,
first we will need to create an App Registration in the Azure AD portal and
configure it with the Certificate, SPO API permissions etc. Here is a detailed
walk-through on this in the Microsoft docs: https://docs.microsoft.com/en-us/sharepoint/dev/solution-guidance/security-apponly-azuread
Let's have a look at a few important bits of my Azure AD app registration:
The certificate:
The consented SharePoint permissions:
Once the Azure AD App Registration is configured correctly, we can start
looking at the code.
We will be using a .NET Core 3.1 Console app project for this along with the
following nuget packages:
And finally, here is the code which uses MSAL.NET to get the access token and
attaches it to the .NET Standard CSOM requests going to SharePoint:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using Microsoft.Identity.Client; | |
using Microsoft.SharePoint.Client; | |
using System; | |
using System.Security.Cryptography.X509Certificates; | |
using System.Threading.Tasks; | |
namespace CSOM.NET.Standard.Test | |
{ | |
class Program | |
{ | |
static async Task Main(string[] args) | |
{ | |
string siteUrl = "https://tenant.sharepoint.com/sites/MyAwesomeSite"; | |
string clientId = "<client-id-of-aad-app-registration>"; //e.g. 01e54f9a-81bc-4dee-b15d-e661ae13f382 | |
string certThumprint = "<your-cert-thumbprint>"; // e.g. CE20E000D53A4C968ED8BA3EFC92C40A2692AE98 | |
//For SharePoint app only auth, the scope will be the SharePoint tenant name followed by /.default | |
var scopes = new string[] { "https://tenant.sharepoint.com/.default" }; | |
//Tenant id can be the tenant domain or it can also be the GUID found in Azure AD properties. | |
string tenantId = "tenant.onmicrosoft.com"; | |
var accessToken = await GetApplicationAuthenticatedClient(clientId, certThumprint, scopes, tenantId); | |
var clientContext = GetClientContextWithAccessToken(siteUrl, accessToken); | |
Web web = clientContext.Web; | |
clientContext.Load(web); | |
clientContext.ExecuteQuery(); | |
Console.WriteLine(web.Title); | |
} | |
internal static async Task<string> GetApplicationAuthenticatedClient(string clientId, string certThumprint, string[] scopes, string tenantId) | |
{ | |
X509Certificate2 certificate = GetAppOnlyCertificate(certThumprint); | |
IConfidentialClientApplication clientApp = ConfidentialClientApplicationBuilder | |
.Create(clientId) | |
.WithCertificate(certificate) | |
.WithTenantId(tenantId) | |
.Build(); | |
AuthenticationResult authResult = await clientApp.AcquireTokenForClient(scopes).ExecuteAsync(); | |
string accessToken = authResult.AccessToken; | |
return accessToken; | |
} | |
public static ClientContext GetClientContextWithAccessToken(string targetUrl, string accessToken) | |
{ | |
ClientContext clientContext = new ClientContext(targetUrl); | |
clientContext.ExecutingWebRequest += | |
delegate (object oSender, WebRequestEventArgs webRequestEventArgs) | |
{ | |
webRequestEventArgs.WebRequestExecutor.RequestHeaders["Authorization"] = | |
"Bearer " + accessToken; | |
}; | |
return clientContext; | |
} | |
private static X509Certificate2 GetAppOnlyCertificate(string thumbPrint) | |
{ | |
X509Certificate2 appOnlyCertificate = null; | |
using (X509Store certStore = new X509Store(StoreName.My, StoreLocation.CurrentUser)) | |
{ | |
certStore.Open(OpenFlags.ReadOnly); | |
X509Certificate2Collection certCollection = certStore.Certificates.Find(X509FindType.FindByThumbprint, thumbPrint, false); | |
if (certCollection.Count > 0) | |
{ | |
appOnlyCertificate = certCollection[0]; | |
} | |
certStore.Close(); | |
return appOnlyCertificate; | |
} | |
} | |
} | |
} |
Note: Make sure that you are using the right
way to access the certificate as per your scenario. Here, for demo purposes, I
have installed the certificate to my local machine and I am accessing it from
there. In production scenarios, it's recommended to store the certificate in
Azure Key Vault.
More details here
Hope you found this post useful! I am very glad .NET CSOM Standard is finally
available and we are able to use it .NET Core projects going forward. This is
going to make things so much easier!