Get Azure AD app-only access token using certificate on .NET Core

Communicating with SharePoint Online using an app-only access token is invaluable when building non-interactive applications. Here is how you can get an Azure AD app-only access token in .NET Core.

SharePoint Online authentication protocols

When building solutions for SharePoint, before you are allowed to communicate with SharePoint your application must authenticate with SharePoint. SharePoint Online supports two kinds of authentication protocols: one based on ACS, traditionally used by SharePoint add-ins, and one based on Azure Active Directory (AAD).

The main difference between the two protocols is that the ACS-based protocol is specific to SharePoint and offers you access only to SharePoint APIs. On the other hand using the AAD-based protocol you can access resources from all Office 365, given your application has sufficient permissions. As a result, using the AAD-based authentication you can build more powerful solutions that span the different capabilities available as a part of Office 365.

User vs. app-only access tokens

When using AAD-based authentication in your solution you can choose between using an access token with user context or an app-only access token. While both types of access tokens allow you to communicate with SharePoint, they have their specific limitations. Using SharePoint Search is for example allowed only using an access token with user context. On the other hand, obtaining an access token with user context requires user interaction and therefore is not something you could do in a daemon process.

App-only access tokens and SharePoint Online

Azure Active Directory allows you to obtain a valid app-only access token in two ways: either by using the client id and client secret of your application or by using the client id and a certificate. While both flows will give you a valid access token, only the access token obtained using a certificate is allowed to be used with SharePoint Online. If you try to use an app-only access token obtained using client id and client secret, SharePoint Online will return the following error:

Unsupported app only token.  

Get app-only access token using certificate in .NET Core

In order to get an app-only access token using a certificate you have to obtain a valid certificate and configure your Azure application to use it. Richard diZerega did a great job documenting the whole process from creating a self-signed certificate to building the application code using it to communicate with SharePoint. When building applications on .NET Core the overall process is the same except for the code part.

While there are some differences in class names and method signatures between .NET Framework and .NET Core the one that's particularly inconvenient in this scenario is that in the Active Directory Authentication Library (ADAL) for .NET Core there is no ClientAssertionCertificate class, which happens to be the class responsible for signing messages in AAD OAuth flow with a certificate. You can however implement the class yourself using the IClientAssertionCertificate interface. Here is how to do it.

The instructions in this article are based on a .NET Core v1.0.1 Console Application created using Visual Studio 2015. If you use a different version of .NET Core or created your project in a different way some of your files might be different and you should ensure that you're merging the suggested changes with your project correctly.

Add assembly references

Before you start writing code, you need to add references to ADAL and the Microsoft.IdentityModel.Tokens package. In your project, open the project.json file and change the dependencies section to:

"dependencies": {
  "Microsoft.NETCore.App": {
    "type": "platform",
    "version": "1.0.1"
  },
  "Microsoft.IdentityModel.Clients.ActiveDirectory": "3.13.8",
  "Microsoft.IdentityModel.Tokens": "5.0.0"
}

Copy certificate to the output folder

Assuming you have a .pfx certificate in your project created as explained by Richard, you will need to copy it to your output folder. To do this, open the project.json file and change the buildOptions section to:

"buildOptions": {
  "emitEntryPoint": true,
  "copyToOutput": {
    "includeFiles": [
      "cert.pfx"
    ]
  }
}

Get an app-only access token using certificate

Next, implement the method to obtain a valid app-only access token for your application using a certificate.

public static async Task<string> GetS2SAccessToken(string authority, string resource, string clientId) {  
    var certPath = Path.Combine(GetCurrentDirectoryFromExecutingAssembly(), "cert.pfx");
    var certfile = File.OpenRead(certPath);
    var certificateBytes = new byte[certfile.Length];
    certfile.Read(certificateBytes, 0, (int)certfile.Length);
    var cert = new X509Certificate2(
        certificateBytes,
        "rencore",
        X509KeyStorageFlags.Exportable |
        X509KeyStorageFlags.MachineKeySet |
        X509KeyStorageFlags.PersistKeySet);

    var certificate = new ClientAssertionCertificate(clientId, cert);
    AuthenticationContext context = new AuthenticationContext(authority);
    AuthenticationResult authenticationResult = await context.AcquireTokenAsync(resource, certificate);
    return authenticationResult.AccessToken;
}

public static string GetCurrentDirectoryFromExecutingAssembly() {  
    var codeBase = typeof(Program).GetTypeInfo().Assembly.CodeBase;

    var uri = new UriBuilder(codeBase);
    var path = Uri.UnescapeDataString(uri.Path);
    return Path.GetDirectoryName(path);
}

The GetS2SAccessToken method is very similar to the code used by Richard. It starts with loading the certificate from the current application directory. This part works slightly different in .NET Core comparing to the .NET Framework so the method uses a helper method to determine the current application directory.

Next, the method loads the contents of the certificate - this part is exactly the same as when using .NET Framework.

Finally the method creates a new instance of the ClientAssertionCertificate class and passes it to the AcquireTokenAsync method to obtain a valid access token.

Where in the .NET Framework the ClientAssertionCertificate class was available as a part of the ADAL library, it's missing in its .NET Core version. Instead you have to use the IClientAssertionCertificate interface to build the class yourself. Here is how to do it:

using Microsoft.IdentityModel.Clients.ActiveDirectory;  
using Microsoft.IdentityModel.Tokens;  
using System.Security.Cryptography;  
using System.Security.Cryptography.X509Certificates;  
using System.Text;

namespace Rencore {  
    internal class ClientAssertionCertificate : IClientAssertionCertificate {
        private X509Certificate2 certificate;

        public string ClientId { get; private set; }

        public string Thumbprint {
            get {
                return Base64UrlEncoder.Encode(certificate.GetCertHash());
            }
        }

        public ClientAssertionCertificate(string clientId, X509Certificate2 certificate) {
            ClientId = clientId;
            this.certificate = certificate;
        }

        public byte[] Sign(string message) {
            using (var key = certificate.GetRSAPrivateKey()) {
                return key.SignData(Encoding.UTF8.GetBytes(message), HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
            }
        }
    }
}

Comparing to .NET Framework, signing data with a certificate on .NET Core is significantly easier. Using the GetRSAPrivateKey method you can directly get a reference to the RSA private key stored in your certificate and use it to sign the message using the SHA256 algorithm used by Azure AD.

The particularly tricky part is returning correct value as the assertion certificate's thumbprint. Based on its name, you might be tempted to return your X509 certificate's thumbprint like:

public string Thumbprint {  
  get {
    return certificate.Thumbprint;
  }
}

If you would do this, AAD would return an error stating that you're trying to use an unknown certificate.

The correct thing to do is to return a base64-encoded value of the x509 certificate's hash.

public string Thumbprint {  
  get {
    return Base64UrlEncoder.Encode(certificate.GetCertHash());
  }
}

Use the app-only access token to communicate with SharePoint

The last part left is to use the obtained app-only access token to communicate with SharePoint Online:

namespace Rencore {  
    public class Program {
        public static void Main(string[] args) {
            var tenantId = "b183855b-32e9-470a-9b14-726979b79ac1";
            var resource = "https://contoso.sharepoint.com";
            var clientId = "61e18c45-190b-4928-98eb-f2a193dd91a2";
            var accessToken = GetS2SAccessToken("https://login.microsoftonline.com/" + tenantId, resource, clientId).Result;

            using (HttpClient client = new HttpClient()) {
                client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
                client.DefaultRequestHeaders.TryAddWithoutValidation("Accept", "application/json;odata=nometadata");

                var rawJson = client.GetStringAsync("https://contoso.sharepoint.com/_api/web?$select=Title").Result;
                Console.WriteLine(rawJson);
            }
        }
    }
}

To obtain an app-only access token you need to know the ID of the tenant to which you are trying to connect. You can get this ID when registering your application in the Azure Management Portal or during the consent flow when your application is being approved to be used in the particular tenant.

Next, you need to specify for which resource you need the access token. If you're connecting to SharePoint it would be the URL of the main Site Collection on your tenant (ie. https://contoso.sharepoint.com). If you would be for example connecting to the Microsoft Graph you would set the resource to https://graph.microsoft.com.

The final part is the client id of your application that you can get from the Azure Management Portal where your application is registered.

With these three pieces of information in place you can obtain an access token. Once you have it, you add it to the request headers of your web request by assigning new instance of the AuthenticationHeaderValue class, using the access token, to the HttpClient.DefaultRequestHeaders.Authorization property.

Summary

Before daemon processes are allowed to communicate with SharePoint Online they have to authenticate first. Using Azure AD-based authentication with app-only access tokens allows your solution to access not only SharePoint but also other services available as a part of Office 365. SharePoint Online only allows using app-only access tokens obtained using a certificate. Getting access tokens using a certificate isn't supported in ADAL on .NET Core by default but can be added to it with little effort.

Comments

comments powered by Disqus