When using SharePoint Online ideally the method to authenticate and getting access tokens is done using the Azure AD App Only. This method includes using of Azure AD App Registration and self signed certificates. This concept is brilliantly explained on the Microsoft’s blog (Granting access via Azure AD App-Only). I highly recommend reading this article. Once all the configuration is done as per guidance from the blog, there are multiple ways to connect to SharePoint. Our interest is Using the Principal and making use of Azure Key Vaults to store Certificate and retrieve it.
Microsoft’s blog make use of the Nuget packages such as SharePointPnPCoreOnline and Microsoft.Azure.KeyVault which are now deprecated and are not maintained. In my opinion there is no harm in using these for sometime as they are being used in many projects already. But these has limitations, such as SharePointPnPCoreOnline cannot be used in the .NetCore framework.
Microsoft highly recommend using the latest packages which are cross platform. So this obviously made me think to write the code using the latest packages, and since there are no samples easily available, thought to post the research (#SharingisCaring 🙂 ).
Please follow the steps
Generate Self signed Certificate
I would not go deep here. As Granting access via Azure AD App-Only has already explained well on how to generate a certificate. I used the PowerShell script which is shared by Microsoft. And I have made gist here, please refer at Create-SelfSignedCertificate.ps1. On running this script, you will be asked to give a password to encrypt your private key, and both the .PFX file and .CER file will be exported to the current folder. Please save these for future use.
Azure App Registration
Steps are simple, you will need an Azure Active Directory Tenant which is linked to an Office/Microsoft 365 Tenant. You must use an account of a user member of the Tenant Global Admins group.
- Login to the Azure Portal
- Go to Azure Active Directory
- From the Left menu bar, click on “App registrations”
- From the Blade on right, click on “New registration”
- Register an Application by giving a meaningful name as follows.
- From the left menu bar, click on Api permissions. And Grant Full Control or what ever you need on SharePoint.
- Again from left menu bar click on “Certificates & secrets”. And upload the certificate “.CER” file which we created in our first step above. This makes our Azure App registration complete.
Create Azure Function
Either directly create one from the Azure Portal or create it from visual studio. This is a very basic step (I am assuming anyone who will read this blog must have basic knowledge about Azure Functions). So, whatever way you create the function, for now we will keep it as is, and no code change if you have created it from visual studio.
For the sake of an example. We have named our Azure Function as “Hello World Function”.
Create a New or use an existing Azure Key Vault
- From the Azure Portal, Under Azure services click on “Create a resource”.
- Search for “Key Vault” in the search services and marketplace search bar.
- Follow the wizard and your Key Vault is ready to use.
Now, either you are using a newly created or an existing Key Vault we need to do few settings.
- Go to the Key Vault, and from left menu bar click on “Certificates”
- Click on Generate/Import
- Import the certificate “.PFX” file which we created from the first step above.
- After successful upload of certificate, from the Left menu bar click on “Access policies”, and click on “Add Access Policy”.
- Choose the Key, Secret, & Certificate Management. Click on “None selected” for Select principal. From the blade which opens up at the right, type in “Hello World Function” in the search bar. Notice that the Azure Function which we created will appear in the list. Please choose and click on “Select”.
- Once the Principal is successfully added and the blade is closed. Notice that the Function is listed as below screen shot. This means that now our Azure Function has permission to access Key Vault secrets.
- This completes configuration of Key Vaults. After this step Azure Function will have permission to access the secrets in Key Vault.
Code samples
- Install following nuget packages
- Azure.Identity
- Azure.Security.KeyVault.Secrets
- PnP.Framework
I would encourage everyone to read about Azure Identity Client Library and especially how DefaultAzureCredential works. The Azure Identity library provides Azure Active Directory token authentication support.
So, the key things to know for the code are as follows
- Secret Name: This would be the name of the certificate you gave while uploading on Key Vault
- Key Vault Name: Name of the Key Vault which you created earlier in above step. To construct the URL to access Key Vault
- SecretClient: This is a class under “Azure.Security.KeyVault.Secrets”. Its object will be created by passing the Key Vault uri and object of DefaultAzureCredentials(). This is how a connection is established with the Key Vault.
const string secretName = "TestName"; | |
var keyVaultName = "test-blog-kv"; | |
var kvUri = $"https://{keyVaultName}.vault.azure.net"; | |
var client = new SecretClient(new Uri(kvUri), new DefaultAzureCredential()); | |
var secret = await client.GetSecretAsync(secretName); |
To understand the above code. It’s important one knows how DefaultAzureCredential() works. It is very seamless whether you working on your development environment (maybe on console application) or Azure Function deployed on Azure.
One might face difficulty in authentication like I did.
Before I transfer my code into Azure Function I was trying things in .NetCore Console App. Every time I used to get an error at the below lines of code.
var client = new SecretClient(new Uri(kvUri), new DefaultAzureCredential());
var secret = await client.GetSecretAsync(secretName); // Threw Exception here, because above line could not establish authenticated connection.
DefaultAzureCredential behind the scene tries to authenticate in series based on Environment Variables, Managed Identity, Visual Studio, Azure CLI, Azure PowerShell etc.
I set up my visual studio 2019 with the Azure account as shown in the link here. Extracts from the link: To authenticate in Visual Studio select the Tools > Options menu to launch the Options dialog. Then navigate to the Azure Service Authentication options to sign in with your Azure Active Directory account.
But to my surprise it did not work. I do not know why. I stopped wasting my time on it and thought to read and understand the source code on GitHub. Upon reading the code, one will understand that “DefaultAzureCredential” has an overload method which accepts an object of class “DefaultAzureCredentialOptions”. This object has properties to exclude authentication methods which we do not want to try and keep the ones which we want to try.
So I decided to choose the simplest one i.e. Azure CLI or Azure PowerShell. One must install CLI or PowerShell Azure module on the machine. Then simply run below CLI command.
az login
This will launch browser where you can login with the Azure account. That’s it.
Now going back to the code, I had to add few extra lines so that it run on Console Application.
const string secretName = "Test"; | |
var keyVaultName = "test-blog-kv"; | |
var kvUri = $"https://{keyVaultName}.vault.azure.net"; | |
// Creating an object and excluding methods which I do not want to be executed. Although these are executed in series, i.e. in my code, due to Enviornment Variables or Visual Studio exception was thrown. | |
// Ideally I would expect, if one method does not work, without breaking code it should move to another one. | |
DefaultAzureCredentialOptions options = new DefaultAzureCredentialOptions(); | |
options.ExcludeEnvironmentCredential = true; | |
options.ExcludeInteractiveBrowserCredential = true; | |
options.ExcludeManagedIdentityCredential = true; | |
options.ExcludeSharedTokenCacheCredential = true; | |
options.ExcludeVisualStudioCodeCredential = true; | |
options.ExcludeVisualStudioCredential = true; | |
var client = new SecretClient(new Uri(kvUri), new DefaultAzureCredential(options)); | |
var secret = await client.GetSecretAsync(secretName); |
Finally I got the value of Certificate from the Key Vault which can then be used in establishing connection with SharePoint.
I did not need to add DefaultAzureCredentialOptions object when I worked on Azure Function and deployed it on Azure.
Full Code for Console App
static async Task Main(string[] args) | |
{ | |
const string secretName = "Test"; | |
var keyVaultName = "test-blog-kv"; | |
var kvUri = $"https://{keyVaultName}.vault.azure.net"; | |
DefaultAzureCredentialOptions options = new DefaultAzureCredentialOptions(); | |
options.ExcludeEnvironmentCredential = true; | |
options.ExcludeInteractiveBrowserCredential = true; | |
options.ExcludeManagedIdentityCredential = true; | |
options.ExcludeSharedTokenCacheCredential = true; | |
options.ExcludeVisualStudioCodeCredential = true; | |
options.ExcludeVisualStudioCredential = true; | |
var client = new SecretClient(new Uri(kvUri), new DefaultAzureCredential(options)); | |
var secret = await client.GetSecretAsync(secretName); | |
var cert = new X509Certificate2(Convert.FromBase64String(secret.Value.Value)); | |
var cc = new PnP.Framework.AuthenticationManager("<App Registration Client ID>", cert, "<Tenant ID>", null, PnP.Framework.AzureEnvironment.Production, null).GetContext("https://Tenant.sharepoint.com/"); | |
cc.Load(cc.Web); | |
cc.ExecuteQuery(); | |
Console.WriteLine(cc.Web.Title); | |
} |
Full Code for Azure Function (.Net Core Framework)
[FunctionName("Function1")] | |
public static async Task<IActionResult> Run( | |
[HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req, | |
ILogger log) | |
{ | |
log.LogInformation("C# HTTP trigger function processed a request."); | |
const string secretName = "Test"; | |
var keyVaultName = "test-blog-kv"; | |
var kvUri = $"https://{keyVaultName}.vault.azure.net"; | |
var client = new SecretClient(new Uri(kvUri), new DefaultAzureCredential()); | |
var secret = await client.GetSecretAsync(secretName); | |
var cert = new X509Certificate2(Convert.FromBase64String(secret.Value.Value)); | |
var cc = new PnP.Framework.AuthenticationManager("<Azure App Registration Client ID>", cert, "<Tenant ID>", null, PnP.Framework.AzureEnvironment.Production, null).GetContext("https://Tenant.sharepoint.com"); | |
cc.Load(cc.Web); | |
cc.ExecuteQuery(); | |
return new OkObjectResult("Title of web: " + cc.Web.Title); | |
} |