Introduction


Sometime back we did an article on how to create a site collection in SharePoint Online using App-Only Policy. In that article we registered an App inside of SharePoint so that our application can access SharePoint Online using this app. 

But what if we want to achieve the same but we do not want to use an App registered inside of SharePoint. What if we want to achieve the same functionality but using an Azure app instead?

In this article we will see how we can achieve the same result but using an Azure AD app.

Also in this article we will make use of Office Dev Patterns and Practices to achieve the same.

We will be creating a daemon app to provision a site collection in our scenario. We will make use of the extension methods from the Office Dev PnP Core Component.

OAuth2 Authentication Flows with Azure AD


Basically there are three flows that we would like to discuss here. 

  1. Authorization Code Grant Flow
  2. Client Credentials Grant Flow
  3. Implicit Grant Flow
We use these flows to get an access token that we can use to authenticate with a resource that trusts Azure AD.
Now each of the above flows have their own use cases.  We will briefly discuss these flows which will help us understand as to what is the best possible flow for our current scenario.

Authorization Code Grant Flow


This is the most common type of authentication flow used in Azure AD. When the application needs you to login, or needs an access token to act on your behalf, it redirects you over to Azure AD’s authorization endpoint to authenticate. User logs in providing his username and password and after that Azure AD redirects the user back to the redirect url specified in the application on successful login. Along with it an authorization code is returned. The application uses this authorization code, which is a short lived code, to obtain access token on user’s behalf. So, in the nutshell, the user does not have to provide its username and password to the application, but instead uses Azure AD for authentication. The application just uses the authorization code returned by the Azure AD after the successful login to get the desired access tokens.

Client Credential Grant Flow


This type of authentication flow is used in scenarios which just requires the app permissions to fetch the access token i.e. user is not required to provide his credentials at any point for authentication. Everything happens with the permission of the app only. It is different from the previous flow in the sense that in the previous scenario the app was impersonating the user whereas in this scenario it is just your application only with no user interaction. We normally use this scenario while building up daemon or service apps. We create a certificate and register it with Azure AD app. The certificate that we register is a public certificate. From within our app we make use of a private certificate to encrypt a string that we will send over to the access token endpoint.

Implicit Grant Flow


This authentication flow is also similar to the Authentication Code Flow. In this as well we get an access token to impersonate the user. However it differs in the respect that here we don’t request an authorization code and the client is issued the access token directly. This is commonly used scenario while implementing the Single Page Applications. 
Now, after going through each of the above authentication flows it makes sense to use the Client Credential Grant flow in our scenario as we don’t want any sort of user interaction and we simply want to make a daemon application to meet our scenario.
Let us get started with implementing the same.

Register an application with your Azure AD tenant


In order to authenticate using Azure Active Directory we need to first create an application in our Azure subscription’s Active Directory tenant.

1. Sign in to the Azure management portal.

2. Click on Active Directory in the left hand navigation.



3. Click the directory tenant where you wish to register the sample application. Then Click on Applications.



4. In the Application Tab, click on ADD button at the bottom.



5. This will open a Dialog Box. Select the first option “Add an application my organization is developing”



6. On the “Tell us about your application” screen, give any name for your application and select “WEB APPLICATION AND/OR WEB API” in the Type. The reason we don’t select the Native Client Application is that we can not create Application Permissions using Native Client Application. In our scenario we will be creating Application Level Permissions as we do not want any sort of user interaction involved.



7. In the “App Properties” screen, give the sign-on url and app id uri. You can give any valid url here.

8. This creates an application for us in the Azure. Click on Configure.



9. Navigate to the “permissions to other applications” section.  Click on Add application.



10. Select “Office 365 SharePoint Online”.



11. Next we need to grant application permissions to this selected application. Since we want to make use of App Only policy, we will make use of Application Permissions. We will give full control of all site collections in our case and click on save.



Create a self-signed certificate


As we have discussed earlier in the article that we need to create certificate which is used in authentication of our application against Azure AD, this leads us to the next step of creating a self-signed certificate. 

Follow the below steps to setup the certificate.

1. In the Visual Studio Command Line run the following command to generate a self-signed certificate.

makecert -r -pe -n "CN=YourCertName" -b 08/09/2016 -e 08/09/2018 -ss My -len 2048

2. Navigate to certificate manager. Select your certificate from Personal > Certificates.



3. Right click on the certificate. Click on All Tasks > Export. This will open the Certification Export Wizard.



4. Click Next.  Select “Yes, export the private key”



5. Click Next. Select the below settings.



6. Click Next. 



7. Click Next. Specify where you want to save the file with file name.pfx.



8. Click Next and then on the next screen click Finish. This will create a .pfx file.

9. Next we need to create a .cer file. Open the Certificate Export Wizard again.

10. Click Next and then select “No, do not export the private key”



11. Click Next and select the below option.



12.  Click Next. Specify where you want to save the file with file name.cer.



13. Click Next and then click Finish. This creates a “.cer” file.

14. Now we need to add this certificate to Azure. Before doing this we need to run couple of PowerShell Scripts. Open PowerShell and run the below commands. 

$cer = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2
$cer.Import("<YourCertificate>.cer")
$bin = $cer.GetRawCertData()
$base64Value = [System.Convert]::ToBase64String($bin)
$bin = $cer.GetCertHash()
$base64Thumbprint = [System.Convert]::ToBase64String($bin)
$keyid = [System.Guid]::NewGuid().ToString()

Associating the Azure Application with the certificate


The next step is to associate the Azure app with the certificate. 

1. To do this navigate to the app from the Azure Management Portal.

2. Click Manage Manifest > Download Manifest.



3. Open the manifest file in Visual Studio and replace the keyCredentials property with your certificate information generated using the PowerShell commands that you ran.

"keyCredentials": [
    {
        "customKeyIdentifier": "<$base64Thumbprint value>",
        "keyId": "<$keyid value>",
        "type": "AsymmetricX509Cert",
        "usage": "Verify",
        "value":  "<$base64Value value>"
    }
],

4. Save the changes and upload the file back to Azure AD App using Manage Manifest > Upload Manifest.

Implementation


Now that we have our Azure app configured to use our self-signed certificate, let us write the actual code that will provision the site collection in SharePoint Online.
Let us create a console application to achieve this.

Open Visual Studio and create a project.



Next step is to install the required NuGet Packages.

  1. PnP Core library for SharePoint Online
  2. Active Directory Authentication Library




“Azure Active Directory Authentication Library enables client application developers to easily authenticate users to cloud or on-premises Active Directory (AD), and then obtain access tokens for securing API calls.”

Once we have the above packages deployed, we are good to start the actual implementation.

Let us start by adding our certificate file to the solution. 



Next we will provide the configuration settings inside the App.config file.



ClientId: This value is available from the Configure tab of the Azure AD application that we created earlier.

ClientCertificatePfx: Path of your .pfx file.

ClientCertificatePfxPassword: Password of your .pfx file.

Resource: The url of your tenant admin site

Authority: In order to fetch the tenant id, simply navigate to your application and click on “VIEW ENDPOINTS”. This will open up a dialog box where you can see GUID. This GUID is your tenant id.





Now, with configurations in place let us start writing the code. In order to connect to SharePoint Online from a Console Application, we will require an access token. 

In order to fetch the access token, we will make use of the certificate.

So, the first step is to fetch the certificate.

private static X509Certificate2 GetCertificate()
 {
 
      string certFile = System.IO.Path.GetFullPath(ConfigurationManager.AppSettings["ClientCertificatePfx"]);
      X509Certificate2 certificate = new X509Certificate2(certFile, configurationManager.AppSettings["ClientCertificatePfxPassword"], X509KeyStorageFlags.MachineKeySet);
 
       return certificate;
}

Next, we need to fetch the access token using this certificate.

private static async Task<string> GetAccessToken(X509Certificate2 certificate)
{
    var authenticationContext = new AuthenticationContext(ConfigurationManager.AppSettings["Authority"], false);
 
    ClientAssertionCertificate cac = new ClientAssertionCertificate(ConfigurationManager.AppSettings["ClientId"], certificate);
 
    var authenticationResult = await authenticationContext.AcquireTokenAsync(ConfigurationManager.AppSettings["Resource"], cac);
 
 
    var accessToken = authenticationResult.AccessToken;
 
    isToken = true;
 
    return accessToken;
 }

Now that we have the access token, we use this access token to create our site collection.

private static void ProvisionSiteCollectionPnP(string accessToken)
{
     try
     {
          using (ClientRuntimeContext context = TokenHelper.GetClientContextWithAccessToken(ConfigurationManager.AppSettings["Resource"].ToString(), accessToken))
          {
             Tenant tenant = new Tenant(context);
             ConsoleColor defaultForeground = Console.ForegroundColor;
             string targetWebUrl = GetInput("Enter the URL of the target site: ", false, defaultForeground);
             if (tenant.SiteExists(targetWebUrl))
             {
                 Console.WriteLine("Site already exists at URL : " + targetWebUrl);
                 Console.ReadLine();
              }
              else
              {
                 tenant.CreateSiteCollection(targetWebUrl, "Geetanjali Dev Pnp Site", "username@tenant.onmicrosoft.com", "STS#0", 1000, 400, 7, 0, 0, 1033);
               }
 
 
 
          }
      }
      catch (Exception ex)
      {
           Console.WriteLine("Exception " + ex.Message);
           Console.ReadLine();
       }
 }

In this we are making use of the Dev PnP extension methods to provision a site collection. 

First we get the client context using the access token we obtained earlier. Next we get the Tenant object using the obtained context. After that using the Dev PnP helper method “SiteExists” we check if there is an existing site collection at the target url for that tenant. In case the site collection does not exist, we create a new site collection using the extension method “CreateSiteCollection”.

At the end of these steps, we are able to create a new site collection into SharePoint Online using Azure application.

The complete code is

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Security.Cryptography.X509Certificates;
using System.Configuration;
using Microsoft.IdentityModel.Clients.ActiveDirectory;
using Microsoft.SharePoint.Client;
using Microsoft.Online.SharePoint.TenantAdministration;
 
namespace PnPSiteProvisioning
{
    class Program
    {
        private static bool isToken = false;
        static void Main(string[] args)
        {
            var certificate = GetCertificate();
 
            var token = GetToken(certificate);
 
 
            while (!isToken)
            {
                System.Threading.Thread.Sleep(new TimeSpan(0, 1, 0));
            }
        }
 
        private static X509Certificate2 GetCertificate()
        {
 
            string certFile = System.IO.Path.GetFullPath(ConfigurationManager.AppSettings["ClientCertificatePfx"]);
            X509Certificate2 certificate = new X509Certificate2(certFile, ConfigurationManager.AppSettings["ClientCertificatePfxPassword"], X509KeyStorageFlags.MachineKeySet);
 
            return certificate;
        }
 
        private static async Task<string> GetToken(X509Certificate2 certificate)
        {
            var accessToken = await GetAccessToken(certificate);
 
            ProvisionSiteCollectionPnP(accessToken);
            return accessToken;
 
        }
        private static async Task<string> GetAccessToken(X509Certificate2 certificate)
        {
            var authenticationContext = new AuthenticationContext(ConfigurationManager.AppSettings["Authority"], false);
 
            ClientAssertionCertificate cac = new ClientAssertionCertificate(ConfigurationManager.AppSettings["ClientId"], certificate);
 
            var authenticationResult = await authenticationContext.AcquireTokenAsync(ConfigurationManager.AppSettings["Resource"], cac);
 
 
            var accessToken = authenticationResult.AccessToken;
 
            isToken = true;
 
            return accessToken;
        }
 
        private static void ProvisionSiteCollectionPnP(string accessToken)
        {
            try
            {
                using (ClientRuntimeContext context = TokenHelper.GetClientContextWithAccessToken(ConfigurationManager.AppSettings["Resource"].ToString(), accessToken))
                {
                    Tenant tenant = new Tenant(context);
                    ConsoleColor defaultForeground = Console.ForegroundColor;
                    string targetWebUrl = GetInput("Enter the URL of the target site: ", false, defaultForeground);
                    if (tenant.SiteExists(targetWebUrl))
                    {
                        Console.WriteLine("Site already exists at URL : " + targetWebUrl);
                        Console.ReadLine();
                    }
                    else
                    {
                        tenant.CreateSiteCollection(targetWebUrl, "Geetanjali Dev Pnp Site", "user@tenant.onmicrosoft.com", "STS#0", 1000, 400, 7, 0, 0, 1033);
                    }
 
 
 
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine("Exception " + ex.Message);
                Console.ReadLine();
            }
        }
 
        private static string GetInput(string label, bool isPassword, ConsoleColor defaultForeground)
        {
            Console.ForegroundColor = ConsoleColor.Green;
            Console.WriteLine("{0} : ", label);
            Console.ForegroundColor = defaultForeground;
 
            string value = "";
 
            for (ConsoleKeyInfo keyInfo = Console.ReadKey(true); keyInfo.Key != ConsoleKey.Enter; keyInfo = Console.ReadKey(true))
            {
                if (keyInfo.Key == ConsoleKey.Backspace)
                {
                    if (value.Length > 0)
                    {
                        value = value.Remove(value.Length - 1);
                        Console.SetCursorPosition(Console.CursorLeft - 1, Console.CursorTop);
                        Console.Write(" ");
                        Console.SetCursorPosition(Console.CursorLeft - 1, Console.CursorTop);
                    }
                }
                else if (keyInfo.Key != ConsoleKey.Enter)
                {
                    if (isPassword)
                    {
                        Console.Write("*");
                    }
                    else
                    {
                        Console.Write(keyInfo.KeyChar);
                    }
                    value += keyInfo.KeyChar;
 
                }
 
            }
            Console.WriteLine("");
 
            return value;
        }
    }
 
     
}

Now, let us check what happens when we run this code.

Before running the code let us navigate to the Office 365 Admin center and see if the site collection we are trying to create already exists or not. As you can see in the image below it does not exist.



Let us run the code and provide the url of the site collection that we want to create.




Once you have ran the code, navigate back to the Office 365 Admin center and check again if the site collection exists or not. This time you will see that the site collection exists. Thus we were able to provision a new site using our console application.






Conclusion


In this article we were able to create a site collection in SharePoint Online using the Azure AD for authentication. We simply registered our application inside of Azure AD tenant and used Azure AD for authentication and fetching the access token. This access token can then be used to provision a site collection inside of SharePoint Online. We made use of the Client Credential Grant Flow so that the application can run without any sort of user interaction.

References


Gallery


You can download the entire solution from TechNet Gallery at https://gallery.technet.microsoft.com/SharePoint-Online-f2aca81a

See Also